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