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