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 :
19 : namespace v8 {
20 : namespace internal {
21 :
22 0 : CompilerDispatcher::Job::Job(BackgroundCompileTask* task_arg)
23 73 : : task(task_arg), has_run(false), aborted(false) {}
24 :
25 : CompilerDispatcher::Job::~Job() = default;
26 :
27 125792 : 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 62896 : isolate->counters()->worker_thread_runtime_call_stats()),
33 : background_compile_timer_(
34 62896 : isolate->counters()->compile_function_on_background()),
35 : taskrunner_(platform->GetForegroundTaskRunner(
36 62896 : 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 691856 : semaphore_for_testing_(0) {
48 62896 : if (trace_compiler_dispatcher_ && !IsEnabled()) {
49 0 : PrintF("CompilerDispatcher: dispatcher is disabled\n");
50 : }
51 62896 : }
52 :
53 188641 : CompilerDispatcher::~CompilerDispatcher() {
54 : // AbortAll must be called before CompilerDispatcher is destroyed.
55 62880 : CHECK(task_manager_->canceled());
56 62881 : }
57 :
58 73 : base::Optional<CompilerDispatcher::JobId> CompilerDispatcher::Enqueue(
59 : const ParseInfo* outer_parse_info, const AstRawString* function_name,
60 0 : const FunctionLiteral* function_literal) {
61 146 : TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"),
62 : "V8.CompilerDispatcherEnqueue");
63 : RuntimeCallTimerScope runtimeTimer(
64 73 : isolate_, RuntimeCallCounterId::kCompileEnqueueOnDispatcher);
65 :
66 73 : 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 73 : static_cast<int>(max_stack_size_)));
72 146 : JobMap::const_iterator it = InsertJob(std::move(job));
73 73 : JobId id = it->first;
74 73 : if (trace_compiler_dispatcher_) {
75 : PrintF("CompilerDispatcher: enqueued job %zu for function literal id %d\n",
76 0 : 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 73 : base::MutexGuard lock(&mutex_);
83 146 : pending_background_jobs_.insert(it->second.get());
84 : }
85 73 : ScheduleMoreWorkerTasksIfNeeded();
86 73 : return base::make_optional(id);
87 : }
88 :
89 2441122 : bool CompilerDispatcher::IsEnabled() const { return FLAG_compiler_dispatcher; }
90 :
91 569221 : bool CompilerDispatcher::IsEnqueued(Handle<SharedFunctionInfo> function) const {
92 569221 : if (jobs_.empty()) return false;
93 150 : return GetJobFor(function) != jobs_.end();
94 : }
95 :
96 10 : bool CompilerDispatcher::IsEnqueued(JobId job_id) const {
97 10 : return jobs_.find(job_id) != jobs_.end();
98 : }
99 :
100 66 : void CompilerDispatcher::RegisterSharedFunctionInfo(
101 : JobId job_id, SharedFunctionInfo function) {
102 : DCHECK_NE(jobs_.find(job_id), jobs_.end());
103 :
104 66 : if (trace_compiler_dispatcher_) {
105 0 : PrintF("CompilerDispatcher: registering ");
106 0 : function->ShortPrint();
107 0 : PrintF(" with job id %zu\n", job_id);
108 : }
109 :
110 : // Make a global handle to the function.
111 : Handle<SharedFunctionInfo> function_handle = Handle<SharedFunctionInfo>::cast(
112 132 : 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 132 : shared_to_unoptimized_job_id_.Set(function_handle, job_id);
119 :
120 : {
121 66 : base::MutexGuard lock(&mutex_);
122 66 : job->function = function_handle;
123 66 : if (job->IsReadyToFinalize(lock)) {
124 : // Schedule an idle task to finalize job if it is ready.
125 31 : ScheduleIdleTaskFromAnyThread(lock);
126 : }
127 : }
128 66 : }
129 :
130 51 : void CompilerDispatcher::WaitForJobIfRunningOnBackground(Job* job) {
131 102 : TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"),
132 : "V8.CompilerDispatcherWaitForBackgroundJob");
133 : RuntimeCallTimerScope runtimeTimer(
134 51 : isolate_, RuntimeCallCounterId::kCompileWaitForDispatcher);
135 :
136 51 : base::MutexGuard lock(&mutex_);
137 51 : if (running_background_jobs_.find(job) == running_background_jobs_.end()) {
138 : pending_background_jobs_.erase(job);
139 51 : return;
140 : }
141 : DCHECK_NULL(main_thread_blocking_on_job_);
142 0 : main_thread_blocking_on_job_ = job;
143 0 : while (main_thread_blocking_on_job_ != nullptr) {
144 0 : main_thread_blocking_signal_.Wait(&mutex_);
145 : }
146 : DCHECK(pending_background_jobs_.find(job) == pending_background_jobs_.end());
147 0 : DCHECK(running_background_jobs_.find(job) == running_background_jobs_.end());
148 : }
149 :
150 46 : bool CompilerDispatcher::FinishNow(Handle<SharedFunctionInfo> function) {
151 92 : TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"),
152 : "V8.CompilerDispatcherFinishNow");
153 : RuntimeCallTimerScope runtimeTimer(
154 46 : isolate_, RuntimeCallCounterId::kCompileFinishNowOnDispatcher);
155 46 : if (trace_compiler_dispatcher_) {
156 0 : PrintF("CompilerDispatcher: finishing ");
157 0 : function->ShortPrint();
158 0 : PrintF(" now\n");
159 : }
160 :
161 46 : JobMap::const_iterator it = GetJobFor(function);
162 46 : CHECK(it != jobs_.end());
163 : Job* job = it->second.get();
164 46 : WaitForJobIfRunningOnBackground(job);
165 :
166 46 : if (!job->has_run) {
167 21 : job->task->Run();
168 21 : job->has_run = true;
169 : }
170 :
171 : DCHECK(job->IsReadyToFinalize(&mutex_));
172 : DCHECK(!job->aborted);
173 : bool success = Compiler::FinalizeBackgroundCompileTask(
174 92 : job->task.get(), function, isolate_, Compiler::KEEP_EXCEPTION);
175 :
176 : DCHECK_NE(success, isolate_->has_pending_exception());
177 46 : RemoveJob(it);
178 46 : return success;
179 : }
180 :
181 7 : void CompilerDispatcher::AbortJob(JobId job_id) {
182 7 : if (trace_compiler_dispatcher_) {
183 0 : PrintF("CompilerDispatcher: aborted job %zu\n", job_id);
184 : }
185 : JobMap::const_iterator job_it = jobs_.find(job_id);
186 7 : Job* job = job_it->second.get();
187 :
188 7 : base::LockGuard<base::Mutex> lock(&mutex_);
189 : pending_background_jobs_.erase(job);
190 7 : if (running_background_jobs_.find(job) == running_background_jobs_.end()) {
191 6 : 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 1 : job->aborted = true;
196 : }
197 7 : }
198 :
199 62879 : void CompilerDispatcher::AbortAll() {
200 62879 : task_manager_->TryAbortAll();
201 :
202 125767 : for (auto& it : jobs_) {
203 5 : WaitForJobIfRunningOnBackground(it.second.get());
204 5 : if (trace_compiler_dispatcher_) {
205 0 : PrintF("CompilerDispatcher: aborted job %zu\n", it.first);
206 : }
207 : }
208 : jobs_.clear();
209 : shared_to_unoptimized_job_id_.Clear();
210 : {
211 62880 : base::MutexGuard lock(&mutex_);
212 : DCHECK(pending_background_jobs_.empty());
213 : DCHECK(running_background_jobs_.empty());
214 : }
215 :
216 62881 : task_manager_->CancelAndWait();
217 62881 : }
218 :
219 121 : CompilerDispatcher::JobMap::const_iterator CompilerDispatcher::GetJobFor(
220 : Handle<SharedFunctionInfo> shared) const {
221 121 : JobId* job_id_ptr = shared_to_unoptimized_job_id_.Find(shared);
222 : JobMap::const_iterator job = jobs_.end();
223 121 : if (job_id_ptr) {
224 : job = jobs_.find(*job_id_ptr);
225 : }
226 121 : return job;
227 : }
228 :
229 43 : void CompilerDispatcher::ScheduleIdleTaskFromAnyThread(
230 : const base::MutexGuard&) {
231 59 : if (!taskrunner_->IdleTasksEnabled()) return;
232 43 : if (idle_task_scheduled_) return;
233 :
234 16 : idle_task_scheduled_ = true;
235 : taskrunner_->PostIdleTask(MakeCancelableIdleTask(
236 : task_manager_.get(),
237 111 : [this](double deadline_in_seconds) { DoIdleWork(deadline_in_seconds); }));
238 : }
239 :
240 73 : void CompilerDispatcher::ScheduleMoreWorkerTasksIfNeeded() {
241 146 : TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"),
242 : "V8.CompilerDispatcherScheduleMoreWorkerTasksIfNeeded");
243 : {
244 73 : base::MutexGuard lock(&mutex_);
245 73 : if (pending_background_jobs_.empty()) return;
246 73 : if (platform_->NumberOfWorkerThreads() <= num_worker_tasks_) {
247 : return;
248 : }
249 62 : ++num_worker_tasks_;
250 : }
251 : platform_->CallOnWorkerThread(
252 413 : MakeCancelableTask(task_manager_.get(), [this] { DoBackgroundWork(); }));
253 : }
254 :
255 41 : void CompilerDispatcher::DoBackgroundWork() {
256 82 : TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"),
257 : "V8.CompilerDispatcherDoBackgroundWork");
258 : for (;;) {
259 85 : Job* job = nullptr;
260 : {
261 85 : base::MutexGuard lock(&mutex_);
262 85 : if (!pending_background_jobs_.empty()) {
263 : auto it = pending_background_jobs_.begin();
264 44 : job = *it;
265 : pending_background_jobs_.erase(it);
266 : running_background_jobs_.insert(job);
267 : }
268 : }
269 85 : if (job == nullptr) break;
270 :
271 44 : if (V8_UNLIKELY(block_for_testing_.Value())) {
272 : block_for_testing_.SetValue(false);
273 1 : semaphore_for_testing_.Wait();
274 : }
275 :
276 44 : if (trace_compiler_dispatcher_) {
277 0 : PrintF("CompilerDispatcher: doing background work\n");
278 : }
279 :
280 88 : job->task->Run();
281 :
282 : {
283 : base::MutexGuard lock(&mutex_);
284 : running_background_jobs_.erase(job);
285 :
286 44 : job->has_run = true;
287 44 : 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 10 : ScheduleIdleTaskFromAnyThread(lock);
291 : }
292 :
293 44 : if (main_thread_blocking_on_job_ == job) {
294 1 : main_thread_blocking_on_job_ = nullptr;
295 1 : main_thread_blocking_signal_.NotifyOne();
296 : }
297 : }
298 44 : }
299 :
300 : {
301 : base::MutexGuard lock(&mutex_);
302 41 : --num_worker_tasks_;
303 41 : }
304 : // Don't touch |this| anymore after this point, as it might have been
305 : // deleted.
306 41 : }
307 :
308 15 : void CompilerDispatcher::DoIdleWork(double deadline_in_seconds) {
309 30 : TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"),
310 : "V8.CompilerDispatcherDoIdleWork");
311 : {
312 15 : base::MutexGuard lock(&mutex_);
313 15 : idle_task_scheduled_ = false;
314 : }
315 :
316 15 : if (trace_compiler_dispatcher_) {
317 : PrintF("CompilerDispatcher: received %0.1lfms of idle time\n",
318 0 : (deadline_in_seconds - platform_->MonotonicallyIncreasingTime()) *
319 0 : static_cast<double>(base::Time::kMillisecondsPerSecond));
320 : }
321 31 : 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 58 : for (it = jobs_.cbegin(); it != jobs_.cend(); ++it) {
327 16 : 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 44 : 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 16 : if (!job->aborted) {
342 : Compiler::FinalizeBackgroundCompileTask(
343 : job->task.get(), job->function.ToHandleChecked(), isolate_,
344 30 : Compiler::CLEAR_EXCEPTION);
345 : }
346 16 : 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 2 : ScheduleIdleTaskFromAnyThread(lock);
353 2 : }
354 : }
355 :
356 73 : 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 146 : jobs_.insert(std::make_pair(next_job_id_++, std::move(job)));
362 : DCHECK(added);
363 73 : return it;
364 : }
365 :
366 68 : 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 68 : if (job->function.ToHandle(&function)) {
376 61 : GlobalHandles::Destroy(function.location());
377 : }
378 :
379 : // Delete job.
380 136 : return jobs_.erase(it);
381 : }
382 :
383 : } // namespace internal
384 183867 : } // namespace v8
|