OLD | NEW |
---|---|
1 // Copyright 2016 the V8 project authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "src/compiler-dispatcher/compiler-dispatcher.h" | 5 #include "src/compiler-dispatcher/compiler-dispatcher.h" |
6 | 6 |
7 #include "include/v8-platform.h" | 7 #include "include/v8-platform.h" |
8 #include "include/v8.h" | 8 #include "include/v8.h" |
9 #include "src/base/platform/time.h" | 9 #include "src/base/platform/time.h" |
10 #include "src/cancelable-task.h" | 10 #include "src/cancelable-task.h" |
(...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
59 try_catch.ReThrow(); | 59 try_catch.ReThrow(); |
60 } | 60 } |
61 return job->status() != CompileJobStatus::kFailed; | 61 return job->status() != CompileJobStatus::kFailed; |
62 } | 62 } |
63 | 63 |
64 bool IsFinished(CompilerDispatcherJob* job) { | 64 bool IsFinished(CompilerDispatcherJob* job) { |
65 return job->status() == CompileJobStatus::kDone || | 65 return job->status() == CompileJobStatus::kDone || |
66 job->status() == CompileJobStatus::kFailed; | 66 job->status() == CompileJobStatus::kFailed; |
67 } | 67 } |
68 | 68 |
69 bool CanRunOnAnyThread(CompilerDispatcherJob* job) { | |
70 return (job->status() == CompileJobStatus::kReadyToParse && | |
71 job->can_parse_on_background_thread()) || | |
72 (job->status() == CompileJobStatus::kReadyToCompile && | |
73 job->can_compile_on_background_thread()); | |
74 } | |
75 | |
76 void DoNextStepOnBackgroundThread(CompilerDispatcherJob* job) { | |
77 DCHECK(CanRunOnAnyThread(job)); | |
78 switch (job->status()) { | |
79 case CompileJobStatus::kReadyToParse: | |
80 job->Parse(); | |
81 break; | |
82 | |
83 case CompileJobStatus::kReadyToCompile: | |
84 job->Compile(); | |
85 break; | |
86 | |
87 default: | |
88 UNREACHABLE(); | |
89 } | |
90 } | |
91 | |
69 // Theoretically we get 50ms of idle time max, however it's unlikely that | 92 // Theoretically we get 50ms of idle time max, however it's unlikely that |
70 // we'll get all of it so try to be a conservative. | 93 // we'll get all of it so try to be a conservative. |
71 const double kMaxIdleTimeToExpectInMs = 40; | 94 const double kMaxIdleTimeToExpectInMs = 40; |
72 | 95 |
73 } // namespace | 96 } // namespace |
74 | 97 |
98 class CompilerDispatcher::BackgroundTask : public CancelableTask { | |
99 public: | |
100 BackgroundTask(Isolate* isolate, CompilerDispatcher* dispatcher); | |
101 ~BackgroundTask() override; | |
102 | |
103 // CancelableTask implementation. | |
104 void RunInternal() override; | |
105 | |
106 private: | |
107 CompilerDispatcher* dispatcher_; | |
108 | |
109 DISALLOW_COPY_AND_ASSIGN(BackgroundTask); | |
110 }; | |
111 | |
112 CompilerDispatcher::BackgroundTask::BackgroundTask( | |
113 Isolate* isolate, CompilerDispatcher* dispatcher) | |
114 : CancelableTask(isolate), dispatcher_(dispatcher) {} | |
115 | |
116 CompilerDispatcher::BackgroundTask::~BackgroundTask() {} | |
117 | |
118 void CompilerDispatcher::BackgroundTask::RunInternal() { | |
119 dispatcher_->DoBackgroundWork(); | |
120 } | |
121 | |
75 class CompilerDispatcher::IdleTask : public CancelableIdleTask { | 122 class CompilerDispatcher::IdleTask : public CancelableIdleTask { |
76 public: | 123 public: |
77 IdleTask(Isolate* isolate, CompilerDispatcher* dispatcher); | 124 IdleTask(Isolate* isolate, CompilerDispatcher* dispatcher); |
78 ~IdleTask() override; | 125 ~IdleTask() override; |
79 | 126 |
80 // CancelableIdleTask implementation. | 127 // CancelableIdleTask implementation. |
81 void RunInternal(double deadline_in_seconds) override; | 128 void RunInternal(double deadline_in_seconds) override; |
82 | 129 |
83 private: | 130 private: |
84 CompilerDispatcher* dispatcher_; | 131 CompilerDispatcher* dispatcher_; |
(...skipping 10 matching lines...) Expand all Loading... | |
95 void CompilerDispatcher::IdleTask::RunInternal(double deadline_in_seconds) { | 142 void CompilerDispatcher::IdleTask::RunInternal(double deadline_in_seconds) { |
96 dispatcher_->DoIdleWork(deadline_in_seconds); | 143 dispatcher_->DoIdleWork(deadline_in_seconds); |
97 } | 144 } |
98 | 145 |
99 CompilerDispatcher::CompilerDispatcher(Isolate* isolate, Platform* platform, | 146 CompilerDispatcher::CompilerDispatcher(Isolate* isolate, Platform* platform, |
100 size_t max_stack_size) | 147 size_t max_stack_size) |
101 : isolate_(isolate), | 148 : isolate_(isolate), |
102 platform_(platform), | 149 platform_(platform), |
103 max_stack_size_(max_stack_size), | 150 max_stack_size_(max_stack_size), |
104 tracer_(new CompilerDispatcherTracer(isolate_)), | 151 tracer_(new CompilerDispatcherTracer(isolate_)), |
105 idle_task_scheduled_(false) {} | 152 idle_task_scheduled_(false), |
153 num_scheduled_background_tasks_(0), | |
154 num_available_background_jobs_(0), | |
155 main_thread_blocking_on_job_(nullptr) {} | |
106 | 156 |
107 CompilerDispatcher::~CompilerDispatcher() { | 157 CompilerDispatcher::~CompilerDispatcher() { |
108 // To avoid crashing in unit tests due to unfished jobs. | 158 // To avoid crashing in unit tests due to unfished jobs. |
109 AbortAll(BlockingBehavior::kBlock); | 159 AbortAll(BlockingBehavior::kBlock); |
110 } | 160 } |
111 | 161 |
112 bool CompilerDispatcher::Enqueue(Handle<SharedFunctionInfo> function) { | 162 bool CompilerDispatcher::Enqueue(Handle<SharedFunctionInfo> function) { |
113 if (!IsEnabled()) return false; | 163 if (!IsEnabled()) return false; |
114 | 164 |
115 // We only handle functions (no eval / top-level code / wasm) that are | 165 // We only handle functions (no eval / top-level code / wasm) that are |
116 // attached to a script. | 166 // attached to a script. |
117 if (!function->script()->IsScript() || !function->is_function() || | 167 if (!function->script()->IsScript() || !function->is_function() || |
118 function->asm_function() || function->native()) { | 168 function->asm_function() || function->native()) { |
119 return false; | 169 return false; |
120 } | 170 } |
121 | 171 |
122 if (IsEnqueued(function)) return true; | 172 if (IsEnqueued(function)) return true; |
123 std::unique_ptr<CompilerDispatcherJob> job(new CompilerDispatcherJob( | 173 std::unique_ptr<CompilerDispatcherJob> job(new CompilerDispatcherJob( |
124 isolate_, tracer_.get(), function, max_stack_size_)); | 174 isolate_, tracer_.get(), function, max_stack_size_)); |
125 std::pair<int, int> key(Script::cast(function->script())->id(), | 175 std::pair<int, int> key(Script::cast(function->script())->id(), |
126 function->function_literal_id()); | 176 function->function_literal_id()); |
127 jobs_.insert(std::make_pair(key, std::move(job))); | 177 jobs_.insert(std::make_pair(key, std::move(job))); |
128 ScheduleIdleTaskIfNeeded(); | 178 ScheduleIdleTaskIfNeeded(); |
129 return true; | 179 return true; |
130 } | 180 } |
131 | 181 |
132 bool CompilerDispatcher::IsEnabled() const { | 182 bool CompilerDispatcher::IsEnabled() const { |
133 v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate_); | 183 v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate_); |
134 return FLAG_compiler_dispatcher && platform_->IdleTasksEnabled(v8_isolate); | 184 return FLAG_compiler_dispatcher && platform_->IdleTasksEnabled(v8_isolate) && |
185 platform_->NumberOfAvailableBackgroundThreads() > 0; | |
vogelheim
2017/01/02 17:16:24
Generally speaking, it would be nice if the dispat
jochen (gone - plz use gerrit)
2017/01/02 19:39:39
fair point. Since we only schedule background task
| |
135 } | 186 } |
136 | 187 |
137 bool CompilerDispatcher::IsEnqueued(Handle<SharedFunctionInfo> function) const { | 188 bool CompilerDispatcher::IsEnqueued(Handle<SharedFunctionInfo> function) const { |
138 return GetJobFor(function) != jobs_.end(); | 189 return GetJobFor(function) != jobs_.end(); |
139 } | 190 } |
140 | 191 |
192 void CompilerDispatcher::EnsureNotOnBackground(CompilerDispatcherJob* job) { | |
193 base::LockGuard<base::Mutex> lock(&mutex_); | |
194 if (running_background_jobs_.find(job) == running_background_jobs_.end()) { | |
195 pending_background_jobs_.erase(job); | |
vogelheim
2017/01/02 17:16:24
Do you need to update num_available_background_job
jochen (gone - plz use gerrit)
2017/01/02 19:39:39
actually, I don't need that variable anymore, so I
| |
196 return; | |
197 } | |
198 DCHECK_NULL(main_thread_blocking_on_job_); | |
199 main_thread_blocking_on_job_ = job; | |
200 while (main_thread_blocking_on_job_ != nullptr) { | |
201 main_thread_blocking_signal_.Wait(&mutex_); | |
202 } | |
203 DCHECK(pending_background_jobs_.find(job) == pending_background_jobs_.end()); | |
204 } | |
205 | |
141 bool CompilerDispatcher::FinishNow(Handle<SharedFunctionInfo> function) { | 206 bool CompilerDispatcher::FinishNow(Handle<SharedFunctionInfo> function) { |
142 JobMap::const_iterator job = GetJobFor(function); | 207 JobMap::const_iterator job = GetJobFor(function); |
143 CHECK(job != jobs_.end()); | 208 CHECK(job != jobs_.end()); |
144 | 209 |
145 // TODO(jochen): Check if there's an in-flight background task working on this | 210 EnsureNotOnBackground(job->second.get()); |
146 // job. | |
147 while (!IsFinished(job->second.get())) { | 211 while (!IsFinished(job->second.get())) { |
148 DoNextStepOnMainThread(isolate_, job->second.get(), | 212 DoNextStepOnMainThread(isolate_, job->second.get(), |
149 ExceptionHandling::kThrow); | 213 ExceptionHandling::kThrow); |
150 } | 214 } |
151 bool result = job->second->status() != CompileJobStatus::kFailed; | 215 bool result = job->second->status() != CompileJobStatus::kFailed; |
152 job->second->ResetOnMainThread(); | 216 job->second->ResetOnMainThread(); |
153 jobs_.erase(job); | 217 jobs_.erase(job); |
154 return result; | 218 return result; |
155 } | 219 } |
156 | 220 |
157 void CompilerDispatcher::Abort(Handle<SharedFunctionInfo> function, | 221 void CompilerDispatcher::Abort(Handle<SharedFunctionInfo> function, |
158 BlockingBehavior blocking) { | 222 BlockingBehavior blocking) { |
159 USE(blocking); | 223 USE(blocking); |
160 JobMap::const_iterator job = GetJobFor(function); | 224 JobMap::const_iterator job = GetJobFor(function); |
161 CHECK(job != jobs_.end()); | 225 CHECK(job != jobs_.end()); |
162 | 226 |
163 // TODO(jochen): Check if there's an in-flight background task working on this | 227 EnsureNotOnBackground(job->second.get()); |
164 // job. | |
165 job->second->ResetOnMainThread(); | 228 job->second->ResetOnMainThread(); |
166 jobs_.erase(job); | 229 jobs_.erase(job); |
167 } | 230 } |
168 | 231 |
169 void CompilerDispatcher::AbortAll(BlockingBehavior blocking) { | 232 void CompilerDispatcher::AbortAll(BlockingBehavior blocking) { |
170 USE(blocking); | 233 USE(blocking); |
171 // TODO(jochen): Check if there's an in-flight background task working on this | |
172 // job. | |
173 for (auto& kv : jobs_) { | 234 for (auto& kv : jobs_) { |
235 EnsureNotOnBackground(kv.second.get()); | |
174 kv.second->ResetOnMainThread(); | 236 kv.second->ResetOnMainThread(); |
175 } | 237 } |
176 jobs_.clear(); | 238 jobs_.clear(); |
177 } | 239 } |
178 | 240 |
179 CompilerDispatcher::JobMap::const_iterator CompilerDispatcher::GetJobFor( | 241 CompilerDispatcher::JobMap::const_iterator CompilerDispatcher::GetJobFor( |
180 Handle<SharedFunctionInfo> shared) const { | 242 Handle<SharedFunctionInfo> shared) const { |
181 if (!shared->script()->IsScript()) return jobs_.end(); | 243 if (!shared->script()->IsScript()) return jobs_.end(); |
182 std::pair<int, int> key(Script::cast(shared->script())->id(), | 244 std::pair<int, int> key(Script::cast(shared->script())->id(), |
183 shared->function_literal_id()); | 245 shared->function_literal_id()); |
184 auto range = jobs_.equal_range(key); | 246 auto range = jobs_.equal_range(key); |
185 for (auto job = range.first; job != range.second; ++job) { | 247 for (auto job = range.first; job != range.second; ++job) { |
186 if (job->second->IsAssociatedWith(shared)) return job; | 248 if (job->second->IsAssociatedWith(shared)) return job; |
187 } | 249 } |
188 return jobs_.end(); | 250 return jobs_.end(); |
189 } | 251 } |
190 | 252 |
191 void CompilerDispatcher::ScheduleIdleTaskIfNeeded() { | 253 void CompilerDispatcher::ScheduleIdleTaskIfNeeded() { |
192 v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate_); | 254 v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate_); |
193 DCHECK(platform_->IdleTasksEnabled(v8_isolate)); | 255 DCHECK(platform_->IdleTasksEnabled(v8_isolate)); |
194 if (idle_task_scheduled_) return; | 256 if (idle_task_scheduled_) return; |
195 if (jobs_.empty()) return; | 257 if (jobs_.empty()) return; |
196 idle_task_scheduled_ = true; | 258 idle_task_scheduled_ = true; |
197 platform_->CallIdleOnForegroundThread(v8_isolate, | 259 platform_->CallIdleOnForegroundThread(v8_isolate, |
198 new IdleTask(isolate_, this)); | 260 new IdleTask(isolate_, this)); |
199 } | 261 } |
200 | 262 |
263 void CompilerDispatcher::ConsiderJobForBackgroundProcessing( | |
264 CompilerDispatcherJob* job) { | |
265 if (!CanRunOnAnyThread(job)) return; | |
266 { | |
267 base::LockGuard<base::Mutex> lock(&mutex_); | |
268 pending_background_jobs_.insert(job); | |
269 ++num_available_background_jobs_; | |
270 } | |
271 ScheduleMoreBackgroundTasksIfNeeded(); | |
272 } | |
273 | |
274 void CompilerDispatcher::ScheduleMoreBackgroundTasksIfNeeded() { | |
275 base::LockGuard<base::Mutex> lock(&mutex_); | |
276 if (num_available_background_jobs_ == 0) return; | |
277 if (platform_->NumberOfAvailableBackgroundThreads() <= | |
278 num_scheduled_background_tasks_) { | |
279 return; | |
280 } | |
281 ++num_scheduled_background_tasks_; | |
282 platform_->CallOnBackgroundThread(new BackgroundTask(isolate_, this), | |
283 v8::Platform::kShortRunningTask); | |
284 } | |
285 | |
286 void CompilerDispatcher::DoBackgroundWork() { | |
287 CompilerDispatcherJob* job = nullptr; | |
288 { | |
289 base::LockGuard<base::Mutex> lock(&mutex_); | |
290 --num_scheduled_background_tasks_; | |
291 if (!pending_background_jobs_.empty()) { | |
292 auto it = pending_background_jobs_.begin(); | |
293 job = *it; | |
294 pending_background_jobs_.erase(it); | |
295 running_background_jobs_.insert(job); | |
296 --num_available_background_jobs_; | |
297 } | |
298 } | |
299 if (job == nullptr) return; | |
300 DoNextStepOnBackgroundThread(job); | |
301 { | |
302 base::LockGuard<base::Mutex> lock(&mutex_); | |
303 running_background_jobs_.erase(job); | |
304 | |
305 if (main_thread_blocking_on_job_ == job) { | |
306 main_thread_blocking_on_job_ = nullptr; | |
307 main_thread_blocking_signal_.NotifyOne(); | |
308 } | |
309 } | |
310 ScheduleMoreBackgroundTasksIfNeeded(); | |
311 } | |
312 | |
201 void CompilerDispatcher::DoIdleWork(double deadline_in_seconds) { | 313 void CompilerDispatcher::DoIdleWork(double deadline_in_seconds) { |
202 idle_task_scheduled_ = false; | 314 idle_task_scheduled_ = false; |
203 | 315 |
204 // Number of jobs that are unlikely to make progress during any idle callback | 316 // Number of jobs that are unlikely to make progress during any idle callback |
205 // due to their estimated duration. | 317 // due to their estimated duration. |
206 size_t too_long_jobs = 0; | 318 size_t too_long_jobs = 0; |
207 | 319 |
208 // Iterate over all available jobs & remaining time. For each job, decide | 320 // Iterate over all available jobs & remaining time. For each job, decide |
209 // whether to 1) skip it (if it would take too long), 2) erase it (if it's | 321 // whether to 1) skip it (if it would take too long), 2) erase it (if it's |
210 // finished), or 3) make progress on it. | 322 // finished), or 3) make progress on it. |
211 double idle_time_in_seconds = | 323 double idle_time_in_seconds = |
212 deadline_in_seconds - platform_->MonotonicallyIncreasingTime(); | 324 deadline_in_seconds - platform_->MonotonicallyIncreasingTime(); |
213 for (auto job = jobs_.begin(); | 325 for (auto job = jobs_.begin(); |
214 job != jobs_.end() && idle_time_in_seconds > 0.0; | 326 job != jobs_.end() && idle_time_in_seconds > 0.0; |
215 idle_time_in_seconds = | 327 idle_time_in_seconds = |
216 deadline_in_seconds - platform_->MonotonicallyIncreasingTime()) { | 328 deadline_in_seconds - platform_->MonotonicallyIncreasingTime()) { |
329 { | |
330 // Don't work on jobs that are being worked on by background tasks. | |
331 // Similarly, remove jobs we work on from the set of available background | |
332 // jobs. | |
333 base::LockGuard<base::Mutex> lock(&mutex_); | |
334 if (running_background_jobs_.find(job->second.get()) != | |
335 running_background_jobs_.end()) { | |
336 ++job; | |
337 continue; | |
338 } | |
339 auto it = pending_background_jobs_.find(job->second.get()); | |
340 if (it != pending_background_jobs_.end()) { | |
341 pending_background_jobs_.erase(it); | |
342 --num_available_background_jobs_; | |
343 } | |
vogelheim
2017/01/02 17:16:24
This logic is a bit weird, because first we take i
| |
344 } | |
vogelheim
2017/01/02 17:16:24
[opinion ahead; feel free to ignore:]
The can-id
jochen (gone - plz use gerrit)
2017/01/02 19:39:39
the can-idle property can change, when the estimat
vogelheim
2017/01/03 10:32:37
Hmm. Not sure I'm willing to give this up yet. For
jochen (gone - plz use gerrit)
2017/01/03 12:59:35
in d8, we do get predictable idle work calls, howe
| |
217 double estimate_in_ms = job->second->EstimateRuntimeOfNextStepInMs(); | 345 double estimate_in_ms = job->second->EstimateRuntimeOfNextStepInMs(); |
218 if (idle_time_in_seconds < | 346 if (idle_time_in_seconds < |
219 (estimate_in_ms / | 347 (estimate_in_ms / |
220 static_cast<double>(base::Time::kMillisecondsPerSecond))) { | 348 static_cast<double>(base::Time::kMillisecondsPerSecond))) { |
221 // If there's not enough time left, try to estimate whether we would | 349 // If there's not enough time left, try to estimate whether we would |
222 // have managed to finish the job in a large idle task to assess | 350 // have managed to finish the job in a large idle task to assess |
223 // whether we should ask for another idle callback. | 351 // whether we should ask for another idle callback. |
224 if (estimate_in_ms > kMaxIdleTimeToExpectInMs) ++too_long_jobs; | 352 if (estimate_in_ms > kMaxIdleTimeToExpectInMs) ++too_long_jobs; |
353 ConsiderJobForBackgroundProcessing(job->second.get()); | |
225 ++job; | 354 ++job; |
226 } else if (IsFinished(job->second.get())) { | 355 } else if (IsFinished(job->second.get())) { |
227 job->second->ResetOnMainThread(); | 356 job->second->ResetOnMainThread(); |
228 job = jobs_.erase(job); | 357 job = jobs_.erase(job); |
229 break; | 358 break; |
230 } else { | 359 } else { |
231 // Do one step, and keep processing the job (as we don't advance the | 360 // Do one step, and keep processing the job (as we don't advance the |
232 // iterator). | 361 // iterator). |
233 DoNextStepOnMainThread(isolate_, job->second.get(), | 362 DoNextStepOnMainThread(isolate_, job->second.get(), |
234 ExceptionHandling::kSwallow); | 363 ExceptionHandling::kSwallow); |
235 } | 364 } |
236 } | 365 } |
237 if (jobs_.size() > too_long_jobs) ScheduleIdleTaskIfNeeded(); | 366 if (jobs_.size() > too_long_jobs) ScheduleIdleTaskIfNeeded(); |
238 } | 367 } |
239 | 368 |
240 } // namespace internal | 369 } // namespace internal |
241 } // namespace v8 | 370 } // namespace v8 |
OLD | NEW |