1// Copyright 2021 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/baseline/baseline-batch-compiler.h" 6 7// TODO(v8:11421): Remove #if once baseline compiler is ported to other 8// architectures. 9#include "src/flags/flags.h" 10#if ENABLE_SPARKPLUG 11 12#include <algorithm> 13 14#include "src/baseline/baseline-compiler.h" 15#include "src/codegen/compiler.h" 16#include "src/execution/isolate.h" 17#include "src/handles/global-handles-inl.h" 18#include "src/heap/factory-inl.h" 19#include "src/heap/heap-inl.h" 20#include "src/heap/local-heap-inl.h" 21#include "src/heap/parked-scope.h" 22#include "src/objects/fixed-array-inl.h" 23#include "src/objects/js-function-inl.h" 24#include "src/utils/locked-queue-inl.h" 25 26namespace v8 { 27namespace internal { 28namespace baseline { 29 30static bool CanCompileWithConcurrentBaseline(SharedFunctionInfo shared, 31 Isolate* isolate) { 32 return !shared.HasBaselineCode() && CanCompileWithBaseline(isolate, shared); 33} 34 35class BaselineCompilerTask { 36 public: 37 BaselineCompilerTask(Isolate* isolate, PersistentHandles* handles, 38 SharedFunctionInfo sfi) 39 : shared_function_info_(handles->NewHandle(sfi)), 40 bytecode_(handles->NewHandle(sfi.GetBytecodeArray(isolate))) { 41 DCHECK(sfi.is_compiled()); 42 } 43 44 BaselineCompilerTask(const BaselineCompilerTask&) V8_NOEXCEPT = delete; 45 BaselineCompilerTask(BaselineCompilerTask&&) V8_NOEXCEPT = default; 46 47 // Executed in the background thread. 48 void Compile(LocalIsolate* local_isolate) { 49 BaselineCompiler compiler(local_isolate, shared_function_info_, bytecode_); 50 compiler.GenerateCode(); 51 maybe_code_ = local_isolate->heap()->NewPersistentMaybeHandle( 52 compiler.Build(local_isolate)); 53 Handle<Code> code; 54 if (maybe_code_.ToHandle(&code)) { 55 local_isolate->heap()->RegisterCodeObject(code); 56 } 57 } 58 59 // Executed in the main thread. 60 void Install(Isolate* isolate) { 61 Handle<Code> code; 62 if (!maybe_code_.ToHandle(&code)) return; 63 if (FLAG_print_code) { 64 code->Print(); 65 } 66 // Don't install the code if the bytecode has been flushed or has 67 // already some baseline code installed. 68 if (!CanCompileWithConcurrentBaseline(*shared_function_info_, isolate)) { 69 return; 70 } 71 shared_function_info_->set_baseline_code(ToCodeT(*code), kReleaseStore); 72 if (V8_LIKELY(FLAG_use_osr)) { 73 shared_function_info_->GetBytecodeArray(isolate) 74 .RequestOsrAtNextOpportunity(); 75 } 76 if (FLAG_trace_baseline_concurrent_compilation) { 77 CodeTracer::Scope scope(isolate->GetCodeTracer()); 78 std::stringstream ss; 79 ss << "[Concurrent Sparkplug Off Thread] Function "; 80 shared_function_info_->ShortPrint(ss); 81 ss << " installed\n"; 82 OFStream os(scope.file()); 83 os << ss.str(); 84 } 85 } 86 87 private: 88 Handle<SharedFunctionInfo> shared_function_info_; 89 Handle<BytecodeArray> bytecode_; 90 MaybeHandle<Code> maybe_code_; 91}; 92 93class BaselineBatchCompilerJob { 94 public: 95 BaselineBatchCompilerJob(Isolate* isolate, Handle<WeakFixedArray> task_queue, 96 int batch_size) { 97 handles_ = isolate->NewPersistentHandles(); 98 tasks_.reserve(batch_size); 99 for (int i = 0; i < batch_size; i++) { 100 MaybeObject maybe_sfi = task_queue->Get(i); 101 // TODO(victorgomes): Do I need to clear the value? 102 task_queue->Set(i, HeapObjectReference::ClearedValue(isolate)); 103 HeapObject obj; 104 // Skip functions where weak reference is no longer valid. 105 if (!maybe_sfi.GetHeapObjectIfWeak(&obj)) continue; 106 // Skip functions where the bytecode has been flushed. 107 SharedFunctionInfo shared = SharedFunctionInfo::cast(obj); 108 if (!CanCompileWithConcurrentBaseline(shared, isolate)) continue; 109 tasks_.emplace_back(isolate, handles_.get(), shared); 110 } 111 if (FLAG_trace_baseline_concurrent_compilation) { 112 CodeTracer::Scope scope(isolate->GetCodeTracer()); 113 PrintF(scope.file(), "[Concurrent Sparkplug] compiling %zu functions\n", 114 tasks_.size()); 115 } 116 } 117 118 // Executed in the background thread. 119 void Compile(LocalIsolate* local_isolate) { 120 local_isolate->heap()->AttachPersistentHandles(std::move(handles_)); 121 for (auto& task : tasks_) { 122 task.Compile(local_isolate); 123 } 124 // Get the handle back since we'd need them to install the code later. 125 handles_ = local_isolate->heap()->DetachPersistentHandles(); 126 } 127 128 // Executed in the main thread. 129 void Install(Isolate* isolate) { 130 for (auto& task : tasks_) { 131 task.Install(isolate); 132 } 133 } 134 135 private: 136 std::vector<BaselineCompilerTask> tasks_; 137 std::unique_ptr<PersistentHandles> handles_; 138}; 139 140class ConcurrentBaselineCompiler { 141 public: 142 class JobDispatcher : public v8::JobTask { 143 public: 144 JobDispatcher( 145 Isolate* isolate, 146 LockedQueue<std::unique_ptr<BaselineBatchCompilerJob>>* incoming_queue, 147 LockedQueue<std::unique_ptr<BaselineBatchCompilerJob>>* outcoming_queue) 148 : isolate_(isolate), 149 incoming_queue_(incoming_queue), 150 outgoing_queue_(outcoming_queue) {} 151 152 void Run(JobDelegate* delegate) override { 153 LocalIsolate local_isolate(isolate_, ThreadKind::kBackground); 154 UnparkedScope unparked_scope(&local_isolate); 155 LocalHandleScope handle_scope(&local_isolate); 156 157 // Since we're going to compile an entire batch, this guarantees that 158 // we only switch back the memory chunks to RX at the end. 159 CodePageCollectionMemoryModificationScope batch_alloc(isolate_->heap()); 160 161 while (!incoming_queue_->IsEmpty() && !delegate->ShouldYield()) { 162 std::unique_ptr<BaselineBatchCompilerJob> job; 163 if (!incoming_queue_->Dequeue(&job)) break; 164 DCHECK_NOT_NULL(job); 165 job->Compile(&local_isolate); 166 outgoing_queue_->Enqueue(std::move(job)); 167 } 168 isolate_->stack_guard()->RequestInstallBaselineCode(); 169 } 170 171 size_t GetMaxConcurrency(size_t worker_count) const override { 172 size_t max_threads = FLAG_concurrent_sparkplug_max_threads; 173 if (max_threads > 0) { 174 return std::min(max_threads, incoming_queue_->size()); 175 } 176 return incoming_queue_->size(); 177 } 178 179 private: 180 Isolate* isolate_; 181 LockedQueue<std::unique_ptr<BaselineBatchCompilerJob>>* incoming_queue_; 182 LockedQueue<std::unique_ptr<BaselineBatchCompilerJob>>* outgoing_queue_; 183 }; 184 185 explicit ConcurrentBaselineCompiler(Isolate* isolate) : isolate_(isolate) { 186 if (FLAG_concurrent_sparkplug) { 187 job_handle_ = V8::GetCurrentPlatform()->PostJob( 188 TaskPriority::kUserVisible, 189 std::make_unique<JobDispatcher>(isolate_, &incoming_queue_, 190 &outgoing_queue_)); 191 } 192 } 193 194 ~ConcurrentBaselineCompiler() { 195 if (job_handle_ && job_handle_->IsValid()) { 196 // Wait for the job handle to complete, so that we know the queue 197 // pointers are safe. 198 job_handle_->Cancel(); 199 } 200 } 201 202 void CompileBatch(Handle<WeakFixedArray> task_queue, int batch_size) { 203 DCHECK(FLAG_concurrent_sparkplug); 204 RCS_SCOPE(isolate_, RuntimeCallCounterId::kCompileBaseline); 205 incoming_queue_.Enqueue(std::make_unique<BaselineBatchCompilerJob>( 206 isolate_, task_queue, batch_size)); 207 job_handle_->NotifyConcurrencyIncrease(); 208 } 209 210 void InstallBatch() { 211 while (!outgoing_queue_.IsEmpty()) { 212 std::unique_ptr<BaselineBatchCompilerJob> job; 213 outgoing_queue_.Dequeue(&job); 214 job->Install(isolate_); 215 } 216 } 217 218 private: 219 Isolate* isolate_; 220 std::unique_ptr<JobHandle> job_handle_ = nullptr; 221 LockedQueue<std::unique_ptr<BaselineBatchCompilerJob>> incoming_queue_; 222 LockedQueue<std::unique_ptr<BaselineBatchCompilerJob>> outgoing_queue_; 223}; 224 225BaselineBatchCompiler::BaselineBatchCompiler(Isolate* isolate) 226 : isolate_(isolate), 227 compilation_queue_(Handle<WeakFixedArray>::null()), 228 last_index_(0), 229 estimated_instruction_size_(0), 230 enabled_(true) { 231 if (FLAG_concurrent_sparkplug) { 232 concurrent_compiler_ = 233 std::make_unique<ConcurrentBaselineCompiler>(isolate_); 234 } 235} 236 237BaselineBatchCompiler::~BaselineBatchCompiler() { 238 if (!compilation_queue_.is_null()) { 239 GlobalHandles::Destroy(compilation_queue_.location()); 240 compilation_queue_ = Handle<WeakFixedArray>::null(); 241 } 242} 243 244void BaselineBatchCompiler::EnqueueFunction(Handle<JSFunction> function) { 245 Handle<SharedFunctionInfo> shared(function->shared(), isolate_); 246 // Early return if the function is compiled with baseline already or it is not 247 // suitable for baseline compilation. 248 if (shared->HasBaselineCode()) return; 249 if (!CanCompileWithBaseline(isolate_, *shared)) return; 250 251 // Immediately compile the function if batch compilation is disabled. 252 if (!is_enabled()) { 253 IsCompiledScope is_compiled_scope( 254 function->shared().is_compiled_scope(isolate_)); 255 Compiler::CompileBaseline(isolate_, function, Compiler::CLEAR_EXCEPTION, 256 &is_compiled_scope); 257 return; 258 } 259 260 int estimated_size; 261 { 262 DisallowHeapAllocation no_gc; 263 estimated_size = BaselineCompiler::EstimateInstructionSize( 264 shared->GetBytecodeArray(isolate_)); 265 } 266 estimated_instruction_size_ += estimated_size; 267 if (FLAG_trace_baseline_batch_compilation) { 268 CodeTracer::Scope trace_scope(isolate_->GetCodeTracer()); 269 PrintF(trace_scope.file(), 270 "[Baseline batch compilation] Enqueued function "); 271 function->PrintName(trace_scope.file()); 272 PrintF(trace_scope.file(), 273 " with estimated size %d (current budget: %d/%d)\n", estimated_size, 274 estimated_instruction_size_, 275 FLAG_baseline_batch_compilation_threshold); 276 } 277 if (ShouldCompileBatch()) { 278 if (FLAG_trace_baseline_batch_compilation) { 279 CodeTracer::Scope trace_scope(isolate_->GetCodeTracer()); 280 PrintF(trace_scope.file(), 281 "[Baseline batch compilation] Compiling current batch of %d " 282 "functions\n", 283 (last_index_ + 1)); 284 } 285 if (FLAG_concurrent_sparkplug) { 286 Enqueue(shared); 287 concurrent_compiler_->CompileBatch(compilation_queue_, last_index_); 288 ClearBatch(); 289 } else { 290 CompileBatch(function); 291 } 292 } else { 293 Enqueue(shared); 294 } 295} 296 297void BaselineBatchCompiler::Enqueue(Handle<SharedFunctionInfo> shared) { 298 EnsureQueueCapacity(); 299 compilation_queue_->Set(last_index_++, HeapObjectReference::Weak(*shared)); 300} 301 302void BaselineBatchCompiler::InstallBatch() { 303 DCHECK(FLAG_concurrent_sparkplug); 304 concurrent_compiler_->InstallBatch(); 305} 306 307void BaselineBatchCompiler::EnsureQueueCapacity() { 308 if (compilation_queue_.is_null()) { 309 compilation_queue_ = isolate_->global_handles()->Create( 310 *isolate_->factory()->NewWeakFixedArray(kInitialQueueSize, 311 AllocationType::kOld)); 312 return; 313 } 314 if (last_index_ >= compilation_queue_->length()) { 315 Handle<WeakFixedArray> new_queue = 316 isolate_->factory()->CopyWeakFixedArrayAndGrow(compilation_queue_, 317 last_index_); 318 GlobalHandles::Destroy(compilation_queue_.location()); 319 compilation_queue_ = isolate_->global_handles()->Create(*new_queue); 320 } 321} 322 323void BaselineBatchCompiler::CompileBatch(Handle<JSFunction> function) { 324 CodePageCollectionMemoryModificationScope batch_allocation(isolate_->heap()); 325 { 326 IsCompiledScope is_compiled_scope( 327 function->shared().is_compiled_scope(isolate_)); 328 Compiler::CompileBaseline(isolate_, function, Compiler::CLEAR_EXCEPTION, 329 &is_compiled_scope); 330 } 331 for (int i = 0; i < last_index_; i++) { 332 MaybeObject maybe_sfi = compilation_queue_->Get(i); 333 MaybeCompileFunction(maybe_sfi); 334 compilation_queue_->Set(i, HeapObjectReference::ClearedValue(isolate_)); 335 } 336 ClearBatch(); 337} 338 339bool BaselineBatchCompiler::ShouldCompileBatch() const { 340 return estimated_instruction_size_ >= 341 FLAG_baseline_batch_compilation_threshold; 342} 343 344bool BaselineBatchCompiler::MaybeCompileFunction(MaybeObject maybe_sfi) { 345 HeapObject heapobj; 346 // Skip functions where the weak reference is no longer valid. 347 if (!maybe_sfi.GetHeapObjectIfWeak(&heapobj)) return false; 348 Handle<SharedFunctionInfo> shared = 349 handle(SharedFunctionInfo::cast(heapobj), isolate_); 350 // Skip functions where the bytecode has been flushed. 351 if (!shared->is_compiled()) return false; 352 353 IsCompiledScope is_compiled_scope(shared->is_compiled_scope(isolate_)); 354 return Compiler::CompileSharedWithBaseline( 355 isolate_, shared, Compiler::CLEAR_EXCEPTION, &is_compiled_scope); 356} 357 358void BaselineBatchCompiler::ClearBatch() { 359 estimated_instruction_size_ = 0; 360 last_index_ = 0; 361} 362 363} // namespace baseline 364} // namespace internal 365} // namespace v8 366 367#else 368 369namespace v8 { 370namespace internal { 371namespace baseline { 372 373class ConcurrentBaselineCompiler {}; 374 375BaselineBatchCompiler::BaselineBatchCompiler(Isolate* isolate) 376 : isolate_(isolate), 377 compilation_queue_(Handle<WeakFixedArray>::null()), 378 last_index_(0), 379 estimated_instruction_size_(0), 380 enabled_(false) {} 381 382BaselineBatchCompiler::~BaselineBatchCompiler() { 383 if (!compilation_queue_.is_null()) { 384 GlobalHandles::Destroy(compilation_queue_.location()); 385 compilation_queue_ = Handle<WeakFixedArray>::null(); 386 } 387} 388 389void BaselineBatchCompiler::InstallBatch() { UNREACHABLE(); } 390 391} // namespace baseline 392} // namespace internal 393} // namespace v8 394 395#endif 396