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