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 <sstream>
8 :
9 : #include "include/v8-platform.h"
10 : #include "src/api-inl.h"
11 : #include "src/ast/ast-value-factory.h"
12 : #include "src/ast/ast.h"
13 : #include "src/ast/scopes.h"
14 : #include "src/base/platform/semaphore.h"
15 : #include "src/base/template-utils.h"
16 : #include "src/compiler.h"
17 : #include "src/flags.h"
18 : #include "src/handles.h"
19 : #include "src/objects-inl.h"
20 : #include "src/parsing/parse-info.h"
21 : #include "src/parsing/parsing.h"
22 : #include "src/v8.h"
23 : #include "test/unittests/test-helpers.h"
24 : #include "test/unittests/test-utils.h"
25 : #include "testing/gtest/include/gtest/gtest.h"
26 :
27 : namespace v8 {
28 : namespace internal {
29 :
30 : class CompilerDispatcherTestFlags {
31 : public:
32 15 : static void SetFlagsForTest() {
33 15 : CHECK_NULL(save_flags_);
34 15 : save_flags_ = new SaveFlags();
35 15 : FLAG_single_threaded = true;
36 15 : FlagList::EnforceFlagImplications();
37 15 : FLAG_compiler_dispatcher = true;
38 15 : }
39 :
40 15 : static void RestoreFlags() {
41 15 : CHECK_NOT_NULL(save_flags_);
42 15 : delete save_flags_;
43 15 : save_flags_ = nullptr;
44 15 : }
45 :
46 : private:
47 : static SaveFlags* save_flags_;
48 :
49 : DISALLOW_IMPLICIT_CONSTRUCTORS(CompilerDispatcherTestFlags);
50 : };
51 :
52 : SaveFlags* CompilerDispatcherTestFlags::save_flags_ = nullptr;
53 :
54 : class CompilerDispatcherTest : public TestWithNativeContext {
55 : public:
56 15 : CompilerDispatcherTest() = default;
57 15 : ~CompilerDispatcherTest() override = default;
58 :
59 15 : static void SetUpTestCase() {
60 15 : CompilerDispatcherTestFlags::SetFlagsForTest();
61 : TestWithNativeContext ::SetUpTestCase();
62 15 : }
63 :
64 15 : static void TearDownTestCase() {
65 : TestWithNativeContext ::TearDownTestCase();
66 15 : CompilerDispatcherTestFlags::RestoreFlags();
67 15 : }
68 :
69 18 : static base::Optional<CompilerDispatcher::JobId> EnqueueUnoptimizedCompileJob(
70 : CompilerDispatcher* dispatcher, Isolate* isolate,
71 : Handle<SharedFunctionInfo> shared) {
72 : std::unique_ptr<ParseInfo> outer_parse_info =
73 18 : test::OuterParseInfoForShared(isolate, shared);
74 : AstValueFactory* ast_value_factory =
75 18 : outer_parse_info->GetOrCreateAstValueFactory();
76 : AstNodeFactory ast_node_factory(ast_value_factory,
77 18 : outer_parse_info->zone());
78 :
79 : const AstRawString* function_name =
80 18 : ast_value_factory->GetOneByteString("f");
81 : DeclarationScope* script_scope = new (outer_parse_info->zone())
82 18 : DeclarationScope(outer_parse_info->zone(), ast_value_factory);
83 : DeclarationScope* function_scope =
84 : new (outer_parse_info->zone()) DeclarationScope(
85 18 : outer_parse_info->zone(), script_scope, FUNCTION_SCOPE);
86 36 : function_scope->set_start_position(shared->StartPosition());
87 36 : function_scope->set_end_position(shared->EndPosition());
88 : std::vector<void*> pointer_buffer;
89 : ScopedPtrList<Statement> statements(&pointer_buffer);
90 : const FunctionLiteral* function_literal =
91 : ast_node_factory.NewFunctionLiteral(
92 : function_name, function_scope, statements, -1, -1, -1,
93 : FunctionLiteral::kNoDuplicateParameters,
94 : FunctionLiteral::kAnonymousExpression,
95 : FunctionLiteral::kShouldEagerCompile, shared->StartPosition(), true,
96 36 : shared->FunctionLiteralId(isolate), nullptr);
97 :
98 : return dispatcher->Enqueue(outer_parse_info.get(), function_name,
99 36 : function_literal);
100 : }
101 :
102 : private:
103 : DISALLOW_COPY_AND_ASSIGN(CompilerDispatcherTest);
104 : };
105 :
106 : namespace {
107 :
108 : class MockPlatform : public v8::Platform {
109 : public:
110 13 : MockPlatform()
111 : : time_(0.0),
112 : time_step_(0.0),
113 : idle_task_(nullptr),
114 : sem_(0),
115 26 : tracing_controller_(V8::GetCurrentPlatform()->GetTracingController()) {}
116 26 : ~MockPlatform() override {
117 13 : base::MutexGuard lock(&mutex_);
118 13 : EXPECT_TRUE(foreground_tasks_.empty());
119 13 : EXPECT_TRUE(worker_tasks_.empty());
120 26 : EXPECT_TRUE(idle_task_ == nullptr);
121 13 : }
122 :
123 15 : int NumberOfWorkerThreads() override { return 1; }
124 :
125 13 : std::shared_ptr<TaskRunner> GetForegroundTaskRunner(
126 : v8::Isolate* isolate) override {
127 26 : return std::make_shared<MockForegroundTaskRunner>(this);
128 : }
129 :
130 12 : void CallOnWorkerThread(std::unique_ptr<Task> task) override {
131 12 : base::MutexGuard lock(&mutex_);
132 12 : worker_tasks_.push_back(std::move(task));
133 12 : }
134 :
135 0 : void CallDelayedOnWorkerThread(std::unique_ptr<Task> task,
136 : double delay_in_seconds) override {
137 0 : UNREACHABLE();
138 : }
139 :
140 0 : void CallOnForegroundThread(v8::Isolate* isolate, Task* task) override {
141 0 : base::MutexGuard lock(&mutex_);
142 0 : foreground_tasks_.push_back(std::unique_ptr<Task>(task));
143 0 : }
144 :
145 0 : void CallDelayedOnForegroundThread(v8::Isolate* isolate, Task* task,
146 : double delay_in_seconds) override {
147 0 : UNREACHABLE();
148 : }
149 :
150 0 : void CallIdleOnForegroundThread(v8::Isolate* isolate,
151 : IdleTask* task) override {
152 0 : base::MutexGuard lock(&mutex_);
153 0 : ASSERT_TRUE(idle_task_ == nullptr);
154 0 : idle_task_ = task;
155 : }
156 :
157 0 : bool IdleTasksEnabled(v8::Isolate* isolate) override { return true; }
158 :
159 19 : double MonotonicallyIncreasingTime() override {
160 19 : time_ += time_step_;
161 19 : return time_;
162 : }
163 :
164 0 : double CurrentClockTimeMillis() override {
165 0 : return time_ * base::Time::kMillisecondsPerSecond;
166 : }
167 :
168 0 : v8::TracingController* GetTracingController() override {
169 0 : return tracing_controller_;
170 : }
171 :
172 9 : void RunIdleTask(double deadline_in_seconds, double time_step) {
173 9 : time_step_ = time_step;
174 : IdleTask* task;
175 : {
176 9 : base::MutexGuard lock(&mutex_);
177 9 : task = idle_task_;
178 27 : ASSERT_TRUE(idle_task_ != nullptr);
179 9 : idle_task_ = nullptr;
180 : }
181 9 : task->Run(deadline_in_seconds);
182 9 : delete task;
183 : }
184 :
185 22 : bool IdleTaskPending() {
186 22 : base::MutexGuard lock(&mutex_);
187 44 : return idle_task_;
188 : }
189 :
190 14 : bool WorkerTasksPending() {
191 14 : base::MutexGuard lock(&mutex_);
192 28 : return !worker_tasks_.empty();
193 : }
194 :
195 : bool ForegroundTasksPending() {
196 : base::MutexGuard lock(&mutex_);
197 : return !foreground_tasks_.empty();
198 : }
199 :
200 6 : void RunWorkerTasksAndBlock(Platform* platform) {
201 : std::vector<std::unique_ptr<Task>> tasks;
202 : {
203 6 : base::MutexGuard lock(&mutex_);
204 : tasks.swap(worker_tasks_);
205 : }
206 : platform->CallOnWorkerThread(
207 24 : base::make_unique<TaskWrapper>(this, std::move(tasks), true));
208 6 : sem_.Wait();
209 6 : }
210 :
211 2 : void RunWorkerTasks(Platform* platform) {
212 : std::vector<std::unique_ptr<Task>> tasks;
213 : {
214 2 : base::MutexGuard lock(&mutex_);
215 : tasks.swap(worker_tasks_);
216 : }
217 : platform->CallOnWorkerThread(
218 8 : base::make_unique<TaskWrapper>(this, std::move(tasks), false));
219 2 : }
220 :
221 : void RunForegroundTasks() {
222 : std::vector<std::unique_ptr<Task>> tasks;
223 : {
224 : base::MutexGuard lock(&mutex_);
225 : tasks.swap(foreground_tasks_);
226 : }
227 : for (auto& task : tasks) {
228 : task->Run();
229 : // Reset |task| before running the next one.
230 : task.reset();
231 : }
232 : }
233 :
234 5 : void ClearWorkerTasks() {
235 : std::vector<std::unique_ptr<Task>> tasks;
236 : {
237 5 : base::MutexGuard lock(&mutex_);
238 : tasks.swap(worker_tasks_);
239 5 : }
240 5 : }
241 :
242 : void ClearForegroundTasks() {
243 : std::vector<std::unique_ptr<Task>> tasks;
244 : {
245 : base::MutexGuard lock(&mutex_);
246 : tasks.swap(foreground_tasks_);
247 : }
248 : }
249 :
250 1 : void ClearIdleTask() {
251 1 : base::MutexGuard lock(&mutex_);
252 3 : ASSERT_TRUE(idle_task_ != nullptr);
253 1 : delete idle_task_;
254 1 : idle_task_ = nullptr;
255 : }
256 :
257 : private:
258 : class TaskWrapper : public Task {
259 : public:
260 : TaskWrapper(MockPlatform* platform,
261 : std::vector<std::unique_ptr<Task>> tasks, bool signal)
262 16 : : platform_(platform), tasks_(std::move(tasks)), signal_(signal) {}
263 16 : ~TaskWrapper() override = default;
264 :
265 8 : void Run() override {
266 24 : for (auto& task : tasks_) {
267 8 : task->Run();
268 : // Reset |task| before running the next one.
269 : task.reset();
270 : }
271 8 : if (signal_) platform_->sem_.Signal();
272 8 : }
273 :
274 : private:
275 : MockPlatform* platform_;
276 : std::vector<std::unique_ptr<Task>> tasks_;
277 : bool signal_;
278 :
279 : DISALLOW_COPY_AND_ASSIGN(TaskWrapper);
280 : };
281 :
282 13 : class MockForegroundTaskRunner final : public TaskRunner {
283 : public:
284 : explicit MockForegroundTaskRunner(MockPlatform* platform)
285 13 : : platform_(platform) {}
286 :
287 0 : void PostTask(std::unique_ptr<v8::Task> task) override {
288 0 : base::MutexGuard lock(&platform_->mutex_);
289 0 : platform_->foreground_tasks_.push_back(std::move(task));
290 0 : }
291 :
292 0 : void PostDelayedTask(std::unique_ptr<Task> task,
293 : double delay_in_seconds) override {
294 0 : UNREACHABLE();
295 : };
296 :
297 10 : void PostIdleTask(std::unique_ptr<IdleTask> task) override {
298 : DCHECK(IdleTasksEnabled());
299 10 : base::MutexGuard lock(&platform_->mutex_);
300 30 : ASSERT_TRUE(platform_->idle_task_ == nullptr);
301 20 : platform_->idle_task_ = task.release();
302 : }
303 :
304 13 : bool IdleTasksEnabled() override { return true; };
305 :
306 : private:
307 : MockPlatform* platform_;
308 : };
309 :
310 : double time_;
311 : double time_step_;
312 :
313 : // Protects all *_tasks_.
314 : base::Mutex mutex_;
315 :
316 : IdleTask* idle_task_;
317 : std::vector<std::unique_ptr<Task>> worker_tasks_;
318 : std::vector<std::unique_ptr<Task>> foreground_tasks_;
319 :
320 : base::Semaphore sem_;
321 :
322 : v8::TracingController* tracing_controller_;
323 :
324 : DISALLOW_COPY_AND_ASSIGN(MockPlatform);
325 : };
326 :
327 : } // namespace
328 :
329 15129 : TEST_F(CompilerDispatcherTest, Construct) {
330 1 : MockPlatform platform;
331 3 : CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size);
332 2 : dispatcher.AbortAll();
333 1 : }
334 :
335 15129 : TEST_F(CompilerDispatcherTest, IsEnqueued) {
336 1 : MockPlatform platform;
337 3 : CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size);
338 :
339 : Handle<SharedFunctionInfo> shared =
340 1 : test::CreateSharedFunctionInfo(i_isolate(), nullptr);
341 2 : ASSERT_FALSE(shared->is_compiled());
342 2 : ASSERT_FALSE(dispatcher.IsEnqueued(shared));
343 :
344 : base::Optional<CompilerDispatcher::JobId> job_id =
345 1 : EnqueueUnoptimizedCompileJob(&dispatcher, i_isolate(), shared);
346 :
347 1 : ASSERT_TRUE(job_id);
348 2 : ASSERT_TRUE(dispatcher.IsEnqueued(*job_id));
349 2 : ASSERT_FALSE(dispatcher.IsEnqueued(shared)); // SFI not yet registered.
350 :
351 1 : dispatcher.RegisterSharedFunctionInfo(*job_id, *shared);
352 2 : ASSERT_TRUE(dispatcher.IsEnqueued(*job_id));
353 2 : ASSERT_TRUE(dispatcher.IsEnqueued(shared));
354 :
355 1 : dispatcher.AbortAll();
356 2 : ASSERT_FALSE(dispatcher.IsEnqueued(*job_id));
357 2 : ASSERT_FALSE(dispatcher.IsEnqueued(shared));
358 :
359 2 : ASSERT_FALSE(platform.IdleTaskPending());
360 2 : ASSERT_TRUE(platform.WorkerTasksPending());
361 2 : platform.ClearWorkerTasks();
362 : }
363 :
364 15129 : TEST_F(CompilerDispatcherTest, FinishNow) {
365 1 : MockPlatform platform;
366 3 : CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size);
367 :
368 : Handle<SharedFunctionInfo> shared =
369 1 : test::CreateSharedFunctionInfo(i_isolate(), nullptr);
370 2 : ASSERT_FALSE(shared->is_compiled());
371 :
372 : base::Optional<CompilerDispatcher::JobId> job_id =
373 1 : EnqueueUnoptimizedCompileJob(&dispatcher, i_isolate(), shared);
374 1 : dispatcher.RegisterSharedFunctionInfo(*job_id, *shared);
375 :
376 2 : ASSERT_TRUE(dispatcher.FinishNow(shared));
377 : // Finishing removes the SFI from the queue.
378 2 : ASSERT_FALSE(dispatcher.IsEnqueued(*job_id));
379 2 : ASSERT_FALSE(dispatcher.IsEnqueued(shared));
380 2 : ASSERT_TRUE(shared->is_compiled());
381 :
382 1 : platform.ClearWorkerTasks();
383 2 : ASSERT_FALSE(platform.IdleTaskPending());
384 2 : dispatcher.AbortAll();
385 : }
386 :
387 15129 : TEST_F(CompilerDispatcherTest, CompileAndFinalize) {
388 1 : MockPlatform platform;
389 3 : CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size);
390 :
391 : Handle<SharedFunctionInfo> shared =
392 1 : test::CreateSharedFunctionInfo(i_isolate(), nullptr);
393 2 : ASSERT_FALSE(shared->is_compiled());
394 2 : ASSERT_FALSE(platform.IdleTaskPending());
395 :
396 : base::Optional<CompilerDispatcher::JobId> job_id =
397 1 : EnqueueUnoptimizedCompileJob(&dispatcher, i_isolate(), shared);
398 2 : ASSERT_TRUE(platform.WorkerTasksPending());
399 :
400 : // Run compile steps.
401 1 : platform.RunWorkerTasksAndBlock(V8::GetCurrentPlatform());
402 :
403 : // Since we haven't yet registered the SFI for the job, it should still be
404 : // enqueued and waiting.
405 2 : ASSERT_TRUE(dispatcher.IsEnqueued(*job_id));
406 2 : ASSERT_FALSE(shared->is_compiled());
407 2 : ASSERT_FALSE(platform.IdleTaskPending());
408 :
409 : // Register SFI, which should schedule another idle task to finalize the
410 : // compilation.
411 1 : dispatcher.RegisterSharedFunctionInfo(*job_id, *shared);
412 2 : ASSERT_TRUE(platform.IdleTaskPending());
413 1 : platform.RunIdleTask(1000.0, 0.0);
414 :
415 2 : ASSERT_FALSE(dispatcher.IsEnqueued(shared));
416 2 : ASSERT_TRUE(shared->is_compiled());
417 2 : ASSERT_FALSE(platform.WorkerTasksPending());
418 2 : ASSERT_FALSE(platform.IdleTaskPending());
419 2 : dispatcher.AbortAll();
420 : }
421 :
422 15129 : TEST_F(CompilerDispatcherTest, IdleTaskNoIdleTime) {
423 1 : MockPlatform platform;
424 3 : CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size);
425 :
426 : Handle<SharedFunctionInfo> shared =
427 1 : test::CreateSharedFunctionInfo(i_isolate(), nullptr);
428 2 : ASSERT_FALSE(shared->is_compiled());
429 2 : ASSERT_FALSE(platform.IdleTaskPending());
430 :
431 : base::Optional<CompilerDispatcher::JobId> job_id =
432 1 : EnqueueUnoptimizedCompileJob(&dispatcher, i_isolate(), shared);
433 1 : dispatcher.RegisterSharedFunctionInfo(*job_id, *shared);
434 :
435 : // Run compile steps.
436 1 : platform.RunWorkerTasksAndBlock(V8::GetCurrentPlatform());
437 :
438 : // Job should be ready to finalize.
439 3 : ASSERT_EQ(dispatcher.jobs_.size(), 1u);
440 1 : ASSERT_TRUE(dispatcher.jobs_.begin()->second->has_run);
441 2 : ASSERT_TRUE(platform.IdleTaskPending());
442 :
443 : // Grant no idle time and have time advance beyond it in one step.
444 1 : platform.RunIdleTask(0.0, 1.0);
445 :
446 2 : ASSERT_TRUE(dispatcher.IsEnqueued(shared));
447 2 : ASSERT_FALSE(shared->is_compiled());
448 2 : ASSERT_TRUE(platform.IdleTaskPending());
449 :
450 : // Job should be ready to finalize.
451 3 : ASSERT_EQ(dispatcher.jobs_.size(), 1u);
452 1 : ASSERT_TRUE(dispatcher.jobs_.begin()->second->has_run);
453 :
454 : // Now grant a lot of idle time and freeze time.
455 1 : platform.RunIdleTask(1000.0, 0.0);
456 :
457 2 : ASSERT_FALSE(dispatcher.IsEnqueued(shared));
458 2 : ASSERT_TRUE(shared->is_compiled());
459 2 : ASSERT_FALSE(platform.IdleTaskPending());
460 2 : ASSERT_FALSE(platform.WorkerTasksPending());
461 2 : dispatcher.AbortAll();
462 : }
463 :
464 15129 : TEST_F(CompilerDispatcherTest, IdleTaskSmallIdleTime) {
465 1 : MockPlatform platform;
466 3 : CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size);
467 :
468 : Handle<SharedFunctionInfo> shared_1 =
469 1 : test::CreateSharedFunctionInfo(i_isolate(), nullptr);
470 2 : ASSERT_FALSE(shared_1->is_compiled());
471 : Handle<SharedFunctionInfo> shared_2 =
472 1 : test::CreateSharedFunctionInfo(i_isolate(), nullptr);
473 2 : ASSERT_FALSE(shared_2->is_compiled());
474 :
475 : base::Optional<CompilerDispatcher::JobId> job_id_1 =
476 1 : EnqueueUnoptimizedCompileJob(&dispatcher, i_isolate(), shared_1);
477 : base::Optional<CompilerDispatcher::JobId> job_id_2 =
478 1 : EnqueueUnoptimizedCompileJob(&dispatcher, i_isolate(), shared_2);
479 :
480 1 : dispatcher.RegisterSharedFunctionInfo(*job_id_1, *shared_1);
481 1 : dispatcher.RegisterSharedFunctionInfo(*job_id_2, *shared_2);
482 :
483 : // Run compile steps.
484 1 : platform.RunWorkerTasksAndBlock(V8::GetCurrentPlatform());
485 :
486 : // Both jobs should be ready to finalize.
487 3 : ASSERT_EQ(dispatcher.jobs_.size(), 2u);
488 1 : ASSERT_TRUE(dispatcher.jobs_.begin()->second->has_run);
489 1 : ASSERT_TRUE((++dispatcher.jobs_.begin())->second->has_run);
490 2 : ASSERT_TRUE(platform.IdleTaskPending());
491 :
492 : // Grant a small anount of idle time and have time advance beyond it in one
493 : // step.
494 1 : platform.RunIdleTask(2.0, 1.0);
495 :
496 : // Only one of the jobs should be finalized.
497 3 : ASSERT_EQ(dispatcher.jobs_.size(), 1u);
498 1 : ASSERT_TRUE(dispatcher.jobs_.begin()->second->has_run);
499 1 : ASSERT_NE(dispatcher.IsEnqueued(shared_1), dispatcher.IsEnqueued(shared_2));
500 2 : ASSERT_NE(shared_1->is_compiled(), shared_2->is_compiled());
501 2 : ASSERT_TRUE(platform.IdleTaskPending());
502 :
503 : // Now grant a lot of idle time and freeze time.
504 1 : platform.RunIdleTask(1000.0, 0.0);
505 :
506 2 : ASSERT_FALSE(dispatcher.IsEnqueued(shared_1) ||
507 : dispatcher.IsEnqueued(shared_2));
508 3 : ASSERT_TRUE(shared_1->is_compiled() && shared_2->is_compiled());
509 2 : ASSERT_FALSE(platform.IdleTaskPending());
510 2 : ASSERT_FALSE(platform.WorkerTasksPending());
511 2 : dispatcher.AbortAll();
512 : }
513 :
514 15129 : TEST_F(CompilerDispatcherTest, IdleTaskException) {
515 1 : MockPlatform platform;
516 2 : CompilerDispatcher dispatcher(i_isolate(), &platform, 50);
517 :
518 1 : std::string raw_script("(x) { var a = ");
519 1001 : for (int i = 0; i < 1000; i++) {
520 : // Alternate + and - to avoid n-ary operation nodes.
521 : raw_script += "'x' + 'x' - ";
522 : }
523 : raw_script += " 'x'; };";
524 : test::ScriptResource* script =
525 1 : new test::ScriptResource(raw_script.c_str(), strlen(raw_script.c_str()));
526 : Handle<SharedFunctionInfo> shared =
527 1 : test::CreateSharedFunctionInfo(i_isolate(), script);
528 2 : ASSERT_FALSE(shared->is_compiled());
529 :
530 : base::Optional<CompilerDispatcher::JobId> job_id =
531 1 : EnqueueUnoptimizedCompileJob(&dispatcher, i_isolate(), shared);
532 1 : dispatcher.RegisterSharedFunctionInfo(*job_id, *shared);
533 :
534 : // Run compile steps and finalize.
535 1 : platform.RunWorkerTasksAndBlock(V8::GetCurrentPlatform());
536 1 : platform.RunIdleTask(1000.0, 0.0);
537 :
538 2 : ASSERT_FALSE(dispatcher.IsEnqueued(shared));
539 2 : ASSERT_FALSE(shared->is_compiled());
540 2 : ASSERT_FALSE(i_isolate()->has_pending_exception());
541 1 : platform.ClearWorkerTasks();
542 2 : dispatcher.AbortAll();
543 : }
544 :
545 15129 : TEST_F(CompilerDispatcherTest, FinishNowWithWorkerTask) {
546 1 : MockPlatform platform;
547 3 : CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size);
548 :
549 : Handle<SharedFunctionInfo> shared =
550 1 : test::CreateSharedFunctionInfo(i_isolate(), nullptr);
551 2 : ASSERT_FALSE(shared->is_compiled());
552 :
553 : base::Optional<CompilerDispatcher::JobId> job_id =
554 1 : EnqueueUnoptimizedCompileJob(&dispatcher, i_isolate(), shared);
555 1 : dispatcher.RegisterSharedFunctionInfo(*job_id, *shared);
556 :
557 3 : ASSERT_EQ(dispatcher.jobs_.size(), 1u);
558 2 : ASSERT_FALSE(dispatcher.jobs_.begin()->second->has_run);
559 :
560 2 : ASSERT_TRUE(dispatcher.IsEnqueued(shared));
561 2 : ASSERT_FALSE(shared->is_compiled());
562 3 : ASSERT_EQ(dispatcher.jobs_.size(), 1u);
563 2 : ASSERT_FALSE(dispatcher.jobs_.begin()->second->has_run);
564 2 : ASSERT_TRUE(platform.WorkerTasksPending());
565 :
566 : // This does not block, but races with the FinishNow() call below.
567 1 : platform.RunWorkerTasks(V8::GetCurrentPlatform());
568 :
569 2 : ASSERT_TRUE(dispatcher.FinishNow(shared));
570 : // Finishing removes the SFI from the queue.
571 2 : ASSERT_FALSE(dispatcher.IsEnqueued(shared));
572 2 : ASSERT_TRUE(shared->is_compiled());
573 1 : if (platform.IdleTaskPending()) platform.ClearIdleTask();
574 2 : ASSERT_FALSE(platform.WorkerTasksPending());
575 2 : dispatcher.AbortAll();
576 : }
577 :
578 15129 : TEST_F(CompilerDispatcherTest, IdleTaskMultipleJobs) {
579 1 : MockPlatform platform;
580 3 : CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size);
581 :
582 : Handle<SharedFunctionInfo> shared_1 =
583 1 : test::CreateSharedFunctionInfo(i_isolate(), nullptr);
584 2 : ASSERT_FALSE(shared_1->is_compiled());
585 : Handle<SharedFunctionInfo> shared_2 =
586 1 : test::CreateSharedFunctionInfo(i_isolate(), nullptr);
587 2 : ASSERT_FALSE(shared_2->is_compiled());
588 :
589 : base::Optional<CompilerDispatcher::JobId> job_id_1 =
590 1 : EnqueueUnoptimizedCompileJob(&dispatcher, i_isolate(), shared_1);
591 : base::Optional<CompilerDispatcher::JobId> job_id_2 =
592 1 : EnqueueUnoptimizedCompileJob(&dispatcher, i_isolate(), shared_2);
593 :
594 1 : dispatcher.RegisterSharedFunctionInfo(*job_id_1, *shared_1);
595 1 : dispatcher.RegisterSharedFunctionInfo(*job_id_2, *shared_2);
596 :
597 2 : ASSERT_TRUE(dispatcher.IsEnqueued(shared_1));
598 2 : ASSERT_TRUE(dispatcher.IsEnqueued(shared_2));
599 :
600 : // Run compile steps and finalize.
601 1 : platform.RunWorkerTasksAndBlock(V8::GetCurrentPlatform());
602 1 : platform.RunIdleTask(1000.0, 0.0);
603 :
604 2 : ASSERT_FALSE(dispatcher.IsEnqueued(shared_1));
605 2 : ASSERT_FALSE(dispatcher.IsEnqueued(shared_2));
606 2 : ASSERT_TRUE(shared_1->is_compiled());
607 2 : ASSERT_TRUE(shared_2->is_compiled());
608 2 : ASSERT_FALSE(platform.IdleTaskPending());
609 2 : ASSERT_FALSE(platform.WorkerTasksPending());
610 2 : dispatcher.AbortAll();
611 : }
612 :
613 15129 : TEST_F(CompilerDispatcherTest, FinishNowException) {
614 1 : MockPlatform platform;
615 2 : CompilerDispatcher dispatcher(i_isolate(), &platform, 50);
616 :
617 1 : std::string raw_script("(x) { var a = ");
618 1001 : for (int i = 0; i < 1000; i++) {
619 : // Alternate + and - to avoid n-ary operation nodes.
620 : raw_script += "'x' + 'x' - ";
621 : }
622 : raw_script += " 'x'; };";
623 : test::ScriptResource* script =
624 1 : new test::ScriptResource(raw_script.c_str(), strlen(raw_script.c_str()));
625 : Handle<SharedFunctionInfo> shared =
626 1 : test::CreateSharedFunctionInfo(i_isolate(), script);
627 2 : ASSERT_FALSE(shared->is_compiled());
628 :
629 : base::Optional<CompilerDispatcher::JobId> job_id =
630 1 : EnqueueUnoptimizedCompileJob(&dispatcher, i_isolate(), shared);
631 1 : dispatcher.RegisterSharedFunctionInfo(*job_id, *shared);
632 :
633 2 : ASSERT_FALSE(dispatcher.FinishNow(shared));
634 :
635 2 : ASSERT_FALSE(dispatcher.IsEnqueued(shared));
636 2 : ASSERT_FALSE(shared->is_compiled());
637 2 : ASSERT_TRUE(i_isolate()->has_pending_exception());
638 :
639 1 : i_isolate()->clear_pending_exception();
640 2 : ASSERT_FALSE(platform.IdleTaskPending());
641 1 : platform.ClearWorkerTasks();
642 2 : dispatcher.AbortAll();
643 : }
644 :
645 15129 : TEST_F(CompilerDispatcherTest, AbortJobNotStarted) {
646 1 : MockPlatform platform;
647 3 : CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size);
648 :
649 : Handle<SharedFunctionInfo> shared =
650 1 : test::CreateSharedFunctionInfo(i_isolate(), nullptr);
651 2 : ASSERT_FALSE(shared->is_compiled());
652 :
653 : base::Optional<CompilerDispatcher::JobId> job_id =
654 1 : EnqueueUnoptimizedCompileJob(&dispatcher, i_isolate(), shared);
655 :
656 3 : ASSERT_EQ(dispatcher.jobs_.size(), 1u);
657 2 : ASSERT_FALSE(dispatcher.jobs_.begin()->second->has_run);
658 :
659 2 : ASSERT_TRUE(dispatcher.IsEnqueued(*job_id));
660 2 : ASSERT_FALSE(shared->is_compiled());
661 3 : ASSERT_EQ(dispatcher.jobs_.size(), 1u);
662 2 : ASSERT_FALSE(dispatcher.jobs_.begin()->second->has_run);
663 2 : ASSERT_TRUE(platform.WorkerTasksPending());
664 :
665 1 : dispatcher.AbortJob(*job_id);
666 :
667 : // Aborting removes the job from the queue.
668 2 : ASSERT_FALSE(dispatcher.IsEnqueued(*job_id));
669 2 : ASSERT_FALSE(shared->is_compiled());
670 2 : ASSERT_FALSE(platform.IdleTaskPending());
671 1 : platform.ClearWorkerTasks();
672 2 : dispatcher.AbortAll();
673 : }
674 :
675 15129 : TEST_F(CompilerDispatcherTest, AbortJobAlreadyStarted) {
676 1 : MockPlatform platform;
677 3 : CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size);
678 :
679 : Handle<SharedFunctionInfo> shared =
680 1 : test::CreateSharedFunctionInfo(i_isolate(), nullptr);
681 2 : ASSERT_FALSE(shared->is_compiled());
682 :
683 : base::Optional<CompilerDispatcher::JobId> job_id =
684 1 : EnqueueUnoptimizedCompileJob(&dispatcher, i_isolate(), shared);
685 :
686 3 : ASSERT_EQ(dispatcher.jobs_.size(), 1u);
687 2 : ASSERT_FALSE(dispatcher.jobs_.begin()->second->has_run);
688 :
689 2 : ASSERT_TRUE(dispatcher.IsEnqueued(*job_id));
690 2 : ASSERT_FALSE(shared->is_compiled());
691 3 : ASSERT_EQ(dispatcher.jobs_.size(), 1u);
692 2 : ASSERT_FALSE(dispatcher.jobs_.begin()->second->has_run);
693 2 : ASSERT_TRUE(platform.WorkerTasksPending());
694 :
695 : // Have dispatcher block on the background thread when running the job.
696 : {
697 : base::LockGuard<base::Mutex> lock(&dispatcher.mutex_);
698 : dispatcher.block_for_testing_.SetValue(true);
699 : }
700 :
701 : // Start background thread and wait until it is about to run the job.
702 1 : platform.RunWorkerTasks(V8::GetCurrentPlatform());
703 17574 : while (dispatcher.block_for_testing_.Value()) {
704 : }
705 :
706 : // Now abort while dispatcher is in the middle of running the job.
707 1 : dispatcher.AbortJob(*job_id);
708 :
709 : // Unblock background thread, and wait for job to complete.
710 : {
711 : base::LockGuard<base::Mutex> lock(&dispatcher.mutex_);
712 : dispatcher.main_thread_blocking_on_job_ =
713 1 : dispatcher.jobs_.begin()->second.get();
714 1 : dispatcher.semaphore_for_testing_.Signal();
715 3 : while (dispatcher.main_thread_blocking_on_job_ != nullptr) {
716 1 : dispatcher.main_thread_blocking_signal_.Wait(&dispatcher.mutex_);
717 : }
718 : }
719 :
720 : // Job should have finished running and then been aborted.
721 2 : ASSERT_TRUE(dispatcher.IsEnqueued(*job_id));
722 2 : ASSERT_FALSE(shared->is_compiled());
723 3 : ASSERT_EQ(dispatcher.jobs_.size(), 1u);
724 1 : ASSERT_TRUE(dispatcher.jobs_.begin()->second->has_run);
725 1 : ASSERT_TRUE(dispatcher.jobs_.begin()->second->aborted);
726 2 : ASSERT_FALSE(platform.WorkerTasksPending());
727 2 : ASSERT_TRUE(platform.IdleTaskPending());
728 :
729 : // Runt the pending idle task
730 1 : platform.RunIdleTask(1000.0, 0.0);
731 :
732 : // Aborting removes the SFI from the queue.
733 2 : ASSERT_FALSE(dispatcher.IsEnqueued(*job_id));
734 2 : ASSERT_FALSE(shared->is_compiled());
735 2 : ASSERT_FALSE(platform.IdleTaskPending());
736 2 : ASSERT_FALSE(platform.WorkerTasksPending());
737 2 : dispatcher.AbortAll();
738 : }
739 :
740 15129 : TEST_F(CompilerDispatcherTest, CompileLazyFinishesDispatcherJob) {
741 : // Use the real dispatcher so that CompileLazy checks the same one for
742 : // enqueued functions.
743 1 : CompilerDispatcher* dispatcher = i_isolate()->compiler_dispatcher();
744 :
745 1 : const char raw_script[] = "function lazy() { return 42; }; lazy;";
746 : test::ScriptResource* script =
747 1 : new test::ScriptResource(raw_script, strlen(raw_script));
748 1 : Handle<JSFunction> f = RunJS<JSFunction>(script);
749 2 : Handle<SharedFunctionInfo> shared(f->shared(), i_isolate());
750 2 : ASSERT_FALSE(shared->is_compiled());
751 :
752 : base::Optional<CompilerDispatcher::JobId> job_id =
753 1 : EnqueueUnoptimizedCompileJob(dispatcher, i_isolate(), shared);
754 1 : dispatcher->RegisterSharedFunctionInfo(*job_id, *shared);
755 :
756 : // Now force the function to run and ensure CompileLazy finished and dequeues
757 : // it from the dispatcher.
758 1 : RunJS("lazy();");
759 2 : ASSERT_TRUE(shared->is_compiled());
760 2 : ASSERT_FALSE(dispatcher->IsEnqueued(shared));
761 : }
762 :
763 15129 : TEST_F(CompilerDispatcherTest, CompileLazy2FinishesDispatcherJob) {
764 : // Use the real dispatcher so that CompileLazy checks the same one for
765 : // enqueued functions.
766 1 : CompilerDispatcher* dispatcher = i_isolate()->compiler_dispatcher();
767 :
768 1 : const char raw_source_2[] = "function lazy2() { return 42; }; lazy2;";
769 : test::ScriptResource* source_2 =
770 1 : new test::ScriptResource(raw_source_2, strlen(raw_source_2));
771 1 : Handle<JSFunction> lazy2 = RunJS<JSFunction>(source_2);
772 2 : Handle<SharedFunctionInfo> shared_2(lazy2->shared(), i_isolate());
773 2 : ASSERT_FALSE(shared_2->is_compiled());
774 :
775 1 : const char raw_source_1[] = "function lazy1() { return lazy2(); }; lazy1;";
776 : test::ScriptResource* source_1 =
777 1 : new test::ScriptResource(raw_source_1, strlen(raw_source_1));
778 1 : Handle<JSFunction> lazy1 = RunJS<JSFunction>(source_1);
779 2 : Handle<SharedFunctionInfo> shared_1(lazy1->shared(), i_isolate());
780 2 : ASSERT_FALSE(shared_1->is_compiled());
781 :
782 : base::Optional<CompilerDispatcher::JobId> job_id_1 =
783 1 : EnqueueUnoptimizedCompileJob(dispatcher, i_isolate(), shared_1);
784 1 : dispatcher->RegisterSharedFunctionInfo(*job_id_1, *shared_1);
785 :
786 : base::Optional<CompilerDispatcher::JobId> job_id_2 =
787 1 : EnqueueUnoptimizedCompileJob(dispatcher, i_isolate(), shared_2);
788 1 : dispatcher->RegisterSharedFunctionInfo(*job_id_2, *shared_2);
789 :
790 2 : ASSERT_TRUE(dispatcher->IsEnqueued(shared_1));
791 2 : ASSERT_TRUE(dispatcher->IsEnqueued(shared_2));
792 :
793 1 : RunJS("lazy1();");
794 2 : ASSERT_TRUE(shared_1->is_compiled());
795 2 : ASSERT_TRUE(shared_2->is_compiled());
796 2 : ASSERT_FALSE(dispatcher->IsEnqueued(shared_1));
797 2 : ASSERT_FALSE(dispatcher->IsEnqueued(shared_2));
798 : }
799 :
800 15129 : TEST_F(CompilerDispatcherTest, CompileMultipleOnBackgroundThread) {
801 1 : MockPlatform platform;
802 3 : CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size);
803 :
804 : Handle<SharedFunctionInfo> shared_1 =
805 1 : test::CreateSharedFunctionInfo(i_isolate(), nullptr);
806 2 : ASSERT_FALSE(shared_1->is_compiled());
807 :
808 : Handle<SharedFunctionInfo> shared_2 =
809 1 : test::CreateSharedFunctionInfo(i_isolate(), nullptr);
810 2 : ASSERT_FALSE(shared_2->is_compiled());
811 :
812 : base::Optional<CompilerDispatcher::JobId> job_id_1 =
813 1 : EnqueueUnoptimizedCompileJob(&dispatcher, i_isolate(), shared_1);
814 1 : dispatcher.RegisterSharedFunctionInfo(*job_id_1, *shared_1);
815 :
816 : base::Optional<CompilerDispatcher::JobId> job_id_2 =
817 1 : EnqueueUnoptimizedCompileJob(&dispatcher, i_isolate(), shared_2);
818 1 : dispatcher.RegisterSharedFunctionInfo(*job_id_2, *shared_2);
819 :
820 3 : ASSERT_EQ(dispatcher.jobs_.size(), 2u);
821 2 : ASSERT_FALSE(dispatcher.jobs_.begin()->second->has_run);
822 2 : ASSERT_FALSE((++dispatcher.jobs_.begin())->second->has_run);
823 :
824 2 : ASSERT_TRUE(dispatcher.IsEnqueued(shared_1));
825 2 : ASSERT_TRUE(dispatcher.IsEnqueued(shared_2));
826 2 : ASSERT_FALSE(shared_1->is_compiled());
827 2 : ASSERT_FALSE(shared_2->is_compiled());
828 2 : ASSERT_FALSE(platform.IdleTaskPending());
829 2 : ASSERT_TRUE(platform.WorkerTasksPending());
830 :
831 1 : platform.RunWorkerTasksAndBlock(V8::GetCurrentPlatform());
832 :
833 2 : ASSERT_TRUE(platform.IdleTaskPending());
834 2 : ASSERT_FALSE(platform.WorkerTasksPending());
835 3 : ASSERT_EQ(dispatcher.jobs_.size(), 2u);
836 1 : ASSERT_TRUE(dispatcher.jobs_.begin()->second->has_run);
837 1 : ASSERT_TRUE((++dispatcher.jobs_.begin())->second->has_run);
838 :
839 : // Now grant a lot of idle time and freeze time.
840 1 : platform.RunIdleTask(1000.0, 0.0);
841 :
842 2 : ASSERT_FALSE(dispatcher.IsEnqueued(shared_1));
843 2 : ASSERT_FALSE(dispatcher.IsEnqueued(shared_2));
844 2 : ASSERT_TRUE(shared_1->is_compiled());
845 2 : ASSERT_TRUE(shared_2->is_compiled());
846 2 : ASSERT_FALSE(platform.IdleTaskPending());
847 2 : dispatcher.AbortAll();
848 : }
849 :
850 : } // namespace internal
851 9075 : } // namespace v8
|