// 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/compiler-dispatcher/optimizing-compile-dispatcher.h" #include "src/base/atomicops.h" #include "src/base/template-utils.h" #include "src/cancelable-task.h" #include "src/compiler.h" #include "src/counters.h" #include "src/isolate.h" #include "src/log.h" #include "src/objects-inl.h" #include "src/optimized-compilation-info.h" #include "src/tracing/trace-event.h" #include "src/v8.h" namespace v8 { namespace internal { namespace { void DisposeCompilationJob(OptimizedCompilationJob* job, bool restore_function_code) { if (restore_function_code) { Handle<JSFunction> function = job->compilation_info()->closure(); function->set_code(function->shared()->GetCode()); if (function->IsInOptimizationQueue()) { function->ClearOptimizationMarker(); } // TODO(mvstanton): We can't call EnsureFeedbackVector here due to // allocation, but we probably shouldn't call set_code either, as this // sometimes runs on the worker thread! // JSFunction::EnsureFeedbackVector(function); } delete job; } } // namespace class OptimizingCompileDispatcher::CompileTask : public CancelableTask { public: explicit CompileTask(Isolate* isolate, OptimizingCompileDispatcher* dispatcher) : CancelableTask(isolate), isolate_(isolate), worker_thread_runtime_call_stats_( isolate->counters()->worker_thread_runtime_call_stats()), dispatcher_(dispatcher) { base::MutexGuard lock_guard(&dispatcher_->ref_count_mutex_); ++dispatcher_->ref_count_; } ~CompileTask() override = default; private: // v8::Task overrides. void RunInternal() override { DisallowHeapAllocation no_allocation; DisallowHandleAllocation no_handles; DisallowHandleDereference no_deref; { WorkerThreadRuntimeCallStatsScope runtime_call_stats_scope( worker_thread_runtime_call_stats_); RuntimeCallTimerScope runtimeTimer( runtime_call_stats_scope.Get(), RuntimeCallCounterId::kRecompileConcurrent); TimerEventScope<TimerEventRecompileConcurrent> timer(isolate_); TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"), "V8.RecompileConcurrent"); if (dispatcher_->recompilation_delay_ != 0) { base::OS::Sleep(base::TimeDelta::FromMilliseconds( dispatcher_->recompilation_delay_)); } dispatcher_->CompileNext(dispatcher_->NextInput(true)); } { base::MutexGuard lock_guard(&dispatcher_->ref_count_mutex_); if (--dispatcher_->ref_count_ == 0) { dispatcher_->ref_count_zero_.NotifyOne(); } } } Isolate* isolate_; WorkerThreadRuntimeCallStats* worker_thread_runtime_call_stats_; OptimizingCompileDispatcher* dispatcher_; DISALLOW_COPY_AND_ASSIGN(CompileTask); }; OptimizingCompileDispatcher::~OptimizingCompileDispatcher() { #ifdef DEBUG { base::MutexGuard lock_guard(&ref_count_mutex_); DCHECK_EQ(0, ref_count_); } #endif DCHECK_EQ(0, input_queue_length_); DeleteArray(input_queue_); } OptimizedCompilationJob* OptimizingCompileDispatcher::NextInput( bool check_if_flushing) { base::MutexGuard access_input_queue_(&input_queue_mutex_); if (input_queue_length_ == 0) return nullptr; OptimizedCompilationJob* job = input_queue_[InputQueueIndex(0)]; DCHECK_NOT_NULL(job); input_queue_shift_ = InputQueueIndex(1); input_queue_length_--; if (check_if_flushing) { if (mode_ == FLUSH) { AllowHandleDereference allow_handle_dereference; DisposeCompilationJob(job, true); return nullptr; } } return job; } void OptimizingCompileDispatcher::CompileNext(OptimizedCompilationJob* job) { if (!job) return; // The function may have already been optimized by OSR. Simply continue. CompilationJob::Status status = job->ExecuteJob(); USE(status); // Prevent an unused-variable error. { // 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::MutexGuard access_output_queue_(&output_queue_mutex_); output_queue_.push(job); } isolate_->stack_guard()->RequestInstallCode(); } void OptimizingCompileDispatcher::FlushOutputQueue(bool restore_function_code) { for (;;) { OptimizedCompilationJob* job = nullptr; { base::MutexGuard access_output_queue_(&output_queue_mutex_); if (output_queue_.empty()) return; job = output_queue_.front(); output_queue_.pop(); } DisposeCompilationJob(job, restore_function_code); } } void OptimizingCompileDispatcher::Flush(BlockingBehavior blocking_behavior) { if (blocking_behavior == BlockingBehavior::kDontBlock) { if (FLAG_block_concurrent_recompilation) Unblock(); base::MutexGuard access_input_queue_(&input_queue_mutex_); while (input_queue_length_ > 0) { OptimizedCompilationJob* job = input_queue_[InputQueueIndex(0)]; DCHECK_NOT_NULL(job); input_queue_shift_ = InputQueueIndex(1); input_queue_length_--; DisposeCompilationJob(job, true); } FlushOutputQueue(true); if (FLAG_trace_concurrent_recompilation) { PrintF(" ** Flushed concurrent recompilation queues (not blocking).\n"); } return; } mode_ = FLUSH; if (FLAG_block_concurrent_recompilation) Unblock(); { base::MutexGuard lock_guard(&ref_count_mutex_); while (ref_count_ > 0) ref_count_zero_.Wait(&ref_count_mutex_); mode_ = COMPILE; } FlushOutputQueue(true); if (FLAG_trace_concurrent_recompilation) { PrintF(" ** Flushed concurrent recompilation queues.\n"); } } void OptimizingCompileDispatcher::Stop() { mode_ = FLUSH; if (FLAG_block_concurrent_recompilation) Unblock(); { base::MutexGuard lock_guard(&ref_count_mutex_); while (ref_count_ > 0) ref_count_zero_.Wait(&ref_count_mutex_); mode_ = COMPILE; } if (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(NextInput()); InstallOptimizedFunctions(); } else { FlushOutputQueue(false); } } void OptimizingCompileDispatcher::InstallOptimizedFunctions() { HandleScope handle_scope(isolate_); for (;;) { OptimizedCompilationJob* job = nullptr; { base::MutexGuard access_output_queue_(&output_queue_mutex_); if (output_queue_.empty()) return; job = output_queue_.front(); output_queue_.pop(); } OptimizedCompilationInfo* info = job->compilation_info(); Handle<JSFunction> function(*info->closure(), isolate_); if (function->HasOptimizedCode()) { if (FLAG_trace_concurrent_recompilation) { PrintF(" ** Aborting compilation for "); function->ShortPrint(); PrintF(" as it has already been optimized.\n"); } DisposeCompilationJob(job, false); } else { Compiler::FinalizeOptimizedCompilationJob(job, isolate_); } } } void OptimizingCompileDispatcher::QueueForOptimization( OptimizedCompilationJob* job) { DCHECK(IsQueueAvailable()); { // Add job to the back of the input queue. base::MutexGuard 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 (FLAG_block_concurrent_recompilation) { blocked_jobs_++; } else { V8::GetCurrentPlatform()->CallOnWorkerThread( base::make_unique<CompileTask>(isolate_, this)); } } void OptimizingCompileDispatcher::Unblock() { while (blocked_jobs_ > 0) { V8::GetCurrentPlatform()->CallOnWorkerThread( base::make_unique<CompileTask>(isolate_, this)); blocked_jobs_--; } } } // namespace internal } // namespace v8