// Copyright 2012 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "src/optimizing-compiler-thread.h" #include "src/v8.h" #include "src/base/atomicops.h" #include "src/full-codegen.h" #include "src/hydrogen.h" #include "src/isolate.h" #include "src/v8threads.h" namespace v8 { namespace internal { class OptimizingCompilerThread::CompileTask : public v8::Task { public: CompileTask(Isolate* isolate, OptimizedCompileJob* job) : isolate_(isolate), job_(job) {} virtual ~CompileTask() {} private: // v8::Task overrides. virtual void Run() OVERRIDE { DisallowHeapAllocation no_allocation; DisallowHandleAllocation no_handles; DisallowHandleDereference no_deref; // The function may have already been optimized by OSR. Simply continue. OptimizedCompileJob::Status status = job_->OptimizeGraph(); USE(status); // Prevent an unused-variable error in release mode. DCHECK(status != OptimizedCompileJob::FAILED); // The function may have already been optimized by OSR. Simply continue. // Use a mutex to make sure that functions marked for install // are always also queued. { base::LockGuard lock_guard( &isolate_->optimizing_compiler_thread()->output_queue_mutex_); isolate_->optimizing_compiler_thread()->output_queue_.Enqueue(job_); } isolate_->stack_guard()->RequestInstallCode(); { base::LockGuard lock_guard( &isolate_->optimizing_compiler_thread()->input_queue_mutex_); isolate_->optimizing_compiler_thread()->input_queue_length_--; } isolate_->optimizing_compiler_thread()->input_queue_semaphore_.Signal(); } Isolate* isolate_; OptimizedCompileJob* job_; DISALLOW_COPY_AND_ASSIGN(CompileTask); }; OptimizingCompilerThread::~OptimizingCompilerThread() { DCHECK_EQ(0, input_queue_length_); DeleteArray(input_queue_); if (FLAG_concurrent_osr) { #ifdef DEBUG for (int i = 0; i < osr_buffer_capacity_; i++) { CHECK_EQ(NULL, osr_buffer_[i]); } #endif DeleteArray(osr_buffer_); } } void OptimizingCompilerThread::Run() { #ifdef DEBUG { base::LockGuard lock_guard(&thread_id_mutex_); thread_id_ = ThreadId::Current().ToInteger(); } #endif DisallowHeapAllocation no_allocation; DisallowHandleAllocation no_handles; DisallowHandleDereference no_deref; if (job_based_recompilation_) { return; } base::ElapsedTimer total_timer; if (tracing_enabled_) total_timer.Start(); while (true) { input_queue_semaphore_.Wait(); TimerEventScope timer(isolate_); if (FLAG_concurrent_recompilation_delay != 0) { base::OS::Sleep(FLAG_concurrent_recompilation_delay); } switch (static_cast(base::Acquire_Load(&stop_thread_))) { case CONTINUE: break; case STOP: if (tracing_enabled_) { time_spent_total_ = total_timer.Elapsed(); } stop_semaphore_.Signal(); return; case FLUSH: // The main thread is blocked, waiting for the stop semaphore. { AllowHandleDereference allow_handle_dereference; FlushInputQueue(true); } base::Release_Store(&stop_thread_, static_cast(CONTINUE)); stop_semaphore_.Signal(); // Return to start of consumer loop. continue; } base::ElapsedTimer compiling_timer; if (tracing_enabled_) compiling_timer.Start(); CompileNext(); if (tracing_enabled_) { time_spent_compiling_ += compiling_timer.Elapsed(); } } } OptimizedCompileJob* OptimizingCompilerThread::NextInput() { base::LockGuard access_input_queue_(&input_queue_mutex_); DCHECK(!job_based_recompilation_); if (input_queue_length_ == 0) return NULL; OptimizedCompileJob* job = input_queue_[InputQueueIndex(0)]; DCHECK_NE(NULL, job); input_queue_shift_ = InputQueueIndex(1); input_queue_length_--; return job; } void OptimizingCompilerThread::CompileNext() { OptimizedCompileJob* job = NextInput(); DCHECK_NE(NULL, job); // The function may have already been optimized by OSR. Simply continue. OptimizedCompileJob::Status status = job->OptimizeGraph(); USE(status); // Prevent an unused-variable error in release mode. DCHECK(status != OptimizedCompileJob::FAILED); // The function may have already been optimized by OSR. Simply continue. // Use a mutex to make sure that functions marked for install // are always also queued. output_queue_.Enqueue(job); isolate_->stack_guard()->RequestInstallCode(); } static void DisposeOptimizedCompileJob(OptimizedCompileJob* job, bool restore_function_code) { // The recompile job is allocated in the CompilationInfo's zone. CompilationInfo* info = job->info(); if (restore_function_code) { if (info->is_osr()) { if (!job->IsWaitingForInstall()) { // Remove stack check that guards OSR entry on original code. Handle code = info->unoptimized_code(); uint32_t offset = code->TranslateAstIdToPcOffset(info->osr_ast_id()); BackEdgeTable::RemoveStackCheck(code, offset); } } else { Handle function = info->closure(); function->ReplaceCode(function->shared()->code()); } } delete info; } void OptimizingCompilerThread::FlushInputQueue(bool restore_function_code) { DCHECK(!job_based_recompilation_); OptimizedCompileJob* job; while ((job = NextInput())) { // This should not block, since we have one signal on the input queue // semaphore corresponding to each element in the input queue. input_queue_semaphore_.Wait(); // OSR jobs are dealt with separately. if (!job->info()->is_osr()) { DisposeOptimizedCompileJob(job, restore_function_code); } } } void OptimizingCompilerThread::FlushOutputQueue(bool restore_function_code) { OptimizedCompileJob* job; while (output_queue_.Dequeue(&job)) { // OSR jobs are dealt with separately. if (!job->info()->is_osr()) { DisposeOptimizedCompileJob(job, restore_function_code); } } } void OptimizingCompilerThread::FlushOsrBuffer(bool restore_function_code) { for (int i = 0; i < osr_buffer_capacity_; i++) { if (osr_buffer_[i] != NULL) { DisposeOptimizedCompileJob(osr_buffer_[i], restore_function_code); osr_buffer_[i] = NULL; } } } void OptimizingCompilerThread::Flush() { DCHECK(!IsOptimizerThread()); base::Release_Store(&stop_thread_, static_cast(FLUSH)); if (FLAG_block_concurrent_recompilation) Unblock(); if (!job_based_recompilation_) { input_queue_semaphore_.Signal(); stop_semaphore_.Wait(); } FlushOutputQueue(true); if (FLAG_concurrent_osr) FlushOsrBuffer(true); if (tracing_enabled_) { PrintF(" ** Flushed concurrent recompilation queues.\n"); } } void OptimizingCompilerThread::Stop() { DCHECK(!IsOptimizerThread()); base::Release_Store(&stop_thread_, static_cast(STOP)); if (FLAG_block_concurrent_recompilation) Unblock(); if (!job_based_recompilation_) { input_queue_semaphore_.Signal(); stop_semaphore_.Wait(); } if (job_based_recompilation_) { while (true) { { base::LockGuard access_input_queue(&input_queue_mutex_); if (!input_queue_length_) break; } input_queue_semaphore_.Wait(); } } else if (FLAG_concurrent_recompilation_delay != 0) { // At this point the optimizing compiler thread's event loop has stopped. // There is no need for a mutex when reading input_queue_length_. while (input_queue_length_ > 0) CompileNext(); InstallOptimizedFunctions(); } else { FlushInputQueue(false); FlushOutputQueue(false); } if (FLAG_concurrent_osr) FlushOsrBuffer(false); if (tracing_enabled_) { double percentage = time_spent_compiling_.PercentOf(time_spent_total_); PrintF(" ** Compiler thread did %.2f%% useful work\n", percentage); } if ((FLAG_trace_osr || tracing_enabled_) && FLAG_concurrent_osr) { PrintF("[COSR hit rate %d / %d]\n", osr_hits_, osr_attempts_); } Join(); } void OptimizingCompilerThread::InstallOptimizedFunctions() { DCHECK(!IsOptimizerThread()); HandleScope handle_scope(isolate_); OptimizedCompileJob* job; while (output_queue_.Dequeue(&job)) { CompilationInfo* info = job->info(); Handle function(*info->closure()); if (info->is_osr()) { if (FLAG_trace_osr) { PrintF("[COSR - "); function->ShortPrint(); PrintF(" is ready for install and entry at AST id %d]\n", info->osr_ast_id().ToInt()); } job->WaitForInstall(); // Remove stack check that guards OSR entry on original code. Handle code = info->unoptimized_code(); uint32_t offset = code->TranslateAstIdToPcOffset(info->osr_ast_id()); BackEdgeTable::RemoveStackCheck(code, offset); } else { if (function->IsOptimized()) { if (tracing_enabled_) { PrintF(" ** Aborting compilation for "); function->ShortPrint(); PrintF(" as it has already been optimized.\n"); } DisposeOptimizedCompileJob(job, false); } else { Handle code = Compiler::GetConcurrentlyOptimizedCode(job); function->ReplaceCode( code.is_null() ? function->shared()->code() : *code); } } } } void OptimizingCompilerThread::QueueForOptimization(OptimizedCompileJob* job) { DCHECK(IsQueueAvailable()); DCHECK(!IsOptimizerThread()); CompilationInfo* info = job->info(); if (info->is_osr()) { osr_attempts_++; AddToOsrBuffer(job); // Add job to the front of the input queue. base::LockGuard access_input_queue(&input_queue_mutex_); DCHECK_LT(input_queue_length_, input_queue_capacity_); // Move shift_ back by one. input_queue_shift_ = InputQueueIndex(input_queue_capacity_ - 1); input_queue_[InputQueueIndex(0)] = job; input_queue_length_++; } else { // Add job to the back of the input queue. base::LockGuard access_input_queue(&input_queue_mutex_); DCHECK_LT(input_queue_length_, input_queue_capacity_); input_queue_[InputQueueIndex(input_queue_length_)] = job; input_queue_length_++; } if (job_based_recompilation_) { V8::GetCurrentPlatform()->CallOnBackgroundThread( new CompileTask(isolate_, job), v8::Platform::kShortRunningTask); } else if (FLAG_block_concurrent_recompilation) { blocked_jobs_++; } else { input_queue_semaphore_.Signal(); } } void OptimizingCompilerThread::Unblock() { DCHECK(!IsOptimizerThread()); if (job_based_recompilation_) { return; } while (blocked_jobs_ > 0) { input_queue_semaphore_.Signal(); blocked_jobs_--; } } OptimizedCompileJob* OptimizingCompilerThread::FindReadyOSRCandidate( Handle function, BailoutId osr_ast_id) { DCHECK(!IsOptimizerThread()); for (int i = 0; i < osr_buffer_capacity_; i++) { OptimizedCompileJob* current = osr_buffer_[i]; if (current != NULL && current->IsWaitingForInstall() && current->info()->HasSameOsrEntry(function, osr_ast_id)) { osr_hits_++; osr_buffer_[i] = NULL; return current; } } return NULL; } bool OptimizingCompilerThread::IsQueuedForOSR(Handle function, BailoutId osr_ast_id) { DCHECK(!IsOptimizerThread()); for (int i = 0; i < osr_buffer_capacity_; i++) { OptimizedCompileJob* current = osr_buffer_[i]; if (current != NULL && current->info()->HasSameOsrEntry(function, osr_ast_id)) { return !current->IsWaitingForInstall(); } } return false; } bool OptimizingCompilerThread::IsQueuedForOSR(JSFunction* function) { DCHECK(!IsOptimizerThread()); for (int i = 0; i < osr_buffer_capacity_; i++) { OptimizedCompileJob* current = osr_buffer_[i]; if (current != NULL && *current->info()->closure() == function) { return !current->IsWaitingForInstall(); } } return false; } void OptimizingCompilerThread::AddToOsrBuffer(OptimizedCompileJob* job) { DCHECK(!IsOptimizerThread()); // Find the next slot that is empty or has a stale job. OptimizedCompileJob* stale = NULL; while (true) { stale = osr_buffer_[osr_buffer_cursor_]; if (stale == NULL || stale->IsWaitingForInstall()) break; osr_buffer_cursor_ = (osr_buffer_cursor_ + 1) % osr_buffer_capacity_; } // Add to found slot and dispose the evicted job. if (stale != NULL) { DCHECK(stale->IsWaitingForInstall()); CompilationInfo* info = stale->info(); if (FLAG_trace_osr) { PrintF("[COSR - Discarded "); info->closure()->PrintName(); PrintF(", AST id %d]\n", info->osr_ast_id().ToInt()); } DisposeOptimizedCompileJob(stale, false); } osr_buffer_[osr_buffer_cursor_] = job; osr_buffer_cursor_ = (osr_buffer_cursor_ + 1) % osr_buffer_capacity_; } #ifdef DEBUG bool OptimizingCompilerThread::IsOptimizerThread(Isolate* isolate) { return isolate->concurrent_recompilation_enabled() && isolate->optimizing_compiler_thread()->IsOptimizerThread(); } bool OptimizingCompilerThread::IsOptimizerThread() { base::LockGuard lock_guard(&thread_id_mutex_); return ThreadId::Current().ToInteger() == thread_id_; } #endif } } // namespace v8::internal