baseline-batch-compiler.cc 13.3 KB
Newer Older
1 2 3 4 5 6
// Copyright 2021 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/baseline/baseline-batch-compiler.h"

7 8
// TODO(v8:11421): Remove #if once baseline compiler is ported to other
// architectures.
9 10
#include "src/flags/flags.h"
#if ENABLE_SPARKPLUG
11

12 13
#include <algorithm>

14 15 16
#include "src/baseline/baseline-compiler.h"
#include "src/codegen/compiler.h"
#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 21
#include "src/heap/local-heap-inl.h"
#include "src/heap/parked-scope.h"
22 23
#include "src/objects/fixed-array-inl.h"
#include "src/objects/js-function-inl.h"
24
#include "src/utils/locked-queue-inl.h"
25 26 27 28 29

namespace v8 {
namespace internal {
namespace baseline {

30 31
static bool CanCompileWithConcurrentBaseline(SharedFunctionInfo shared,
                                             Isolate* isolate) {
32
  return !shared.HasBaselineCode() && CanCompileWithBaseline(isolate, shared);
33 34
}

35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
class BaselineCompilerTask {
 public:
  BaselineCompilerTask(Isolate* isolate, PersistentHandles* handles,
                       SharedFunctionInfo sfi)
      : shared_function_info_(handles->NewHandle(sfi)),
        bytecode_(handles->NewHandle(sfi.GetBytecodeArray(isolate))) {
    DCHECK(sfi.is_compiled());
  }

  BaselineCompilerTask(const BaselineCompilerTask&) V8_NOEXCEPT = delete;
  BaselineCompilerTask(BaselineCompilerTask&&) V8_NOEXCEPT = default;

  // Executed in the background thread.
  void Compile(LocalIsolate* local_isolate) {
    BaselineCompiler compiler(local_isolate, shared_function_info_, bytecode_);
    compiler.GenerateCode();
    maybe_code_ = local_isolate->heap()->NewPersistentMaybeHandle(
        compiler.Build(local_isolate));
53 54 55 56
    Handle<Code> code;
    if (maybe_code_.ToHandle(&code)) {
      local_isolate->heap()->RegisterCodeObject(code);
    }
57 58 59 60 61 62 63 64 65
  }

  // Executed in the main thread.
  void Install(Isolate* isolate) {
    Handle<Code> code;
    if (!maybe_code_.ToHandle(&code)) return;
    if (FLAG_print_code) {
      code->Print();
    }
66 67
    // Don't install the code if the bytecode has been flushed or has
    // already some baseline code installed.
68
    if (!CanCompileWithConcurrentBaseline(*shared_function_info_, isolate)) {
69 70
      return;
    }
71
    shared_function_info_->set_baseline_code(ToCodeT(*code), kReleaseStore);
72 73
    if (V8_LIKELY(FLAG_use_osr)) {
      shared_function_info_->GetBytecodeArray(isolate)
74
          .RequestOsrAtNextOpportunity();
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
    }
    if (FLAG_trace_baseline_concurrent_compilation) {
      CodeTracer::Scope scope(isolate->GetCodeTracer());
      std::stringstream ss;
      ss << "[Concurrent Sparkplug Off Thread] Function ";
      shared_function_info_->ShortPrint(ss);
      ss << " installed\n";
      OFStream os(scope.file());
      os << ss.str();
    }
  }

 private:
  Handle<SharedFunctionInfo> shared_function_info_;
  Handle<BytecodeArray> bytecode_;
  MaybeHandle<Code> maybe_code_;
};

class BaselineBatchCompilerJob {
 public:
  BaselineBatchCompilerJob(Isolate* isolate, Handle<WeakFixedArray> task_queue,
96
                           int batch_size) {
97 98 99 100 101 102 103 104 105 106 107
    handles_ = isolate->NewPersistentHandles();
    tasks_.reserve(batch_size);
    for (int i = 0; i < batch_size; i++) {
      MaybeObject maybe_sfi = task_queue->Get(i);
      // TODO(victorgomes): Do I need to clear the value?
      task_queue->Set(i, HeapObjectReference::ClearedValue(isolate));
      HeapObject obj;
      // Skip functions where weak reference is no longer valid.
      if (!maybe_sfi.GetHeapObjectIfWeak(&obj)) continue;
      // Skip functions where the bytecode has been flushed.
      SharedFunctionInfo shared = SharedFunctionInfo::cast(obj);
108
      if (!CanCompileWithConcurrentBaseline(shared, isolate)) continue;
109 110 111 112 113 114 115 116 117 118
      tasks_.emplace_back(isolate, handles_.get(), shared);
    }
    if (FLAG_trace_baseline_concurrent_compilation) {
      CodeTracer::Scope scope(isolate->GetCodeTracer());
      PrintF(scope.file(), "[Concurrent Sparkplug] compiling %zu functions\n",
             tasks_.size());
    }
  }

  // Executed in the background thread.
119 120
  void Compile(LocalIsolate* local_isolate) {
    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 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152
  }

  // Executed in the main thread.
  void Install(Isolate* isolate) {
    for (auto& task : tasks_) {
      task.Install(isolate);
    }
  }

 private:
  std::vector<BaselineCompilerTask> tasks_;
  std::unique_ptr<PersistentHandles> handles_;
};

class ConcurrentBaselineCompiler {
 public:
  class JobDispatcher : public v8::JobTask {
   public:
    JobDispatcher(
        Isolate* isolate,
        LockedQueue<std::unique_ptr<BaselineBatchCompilerJob>>* incoming_queue,
        LockedQueue<std::unique_ptr<BaselineBatchCompilerJob>>* outcoming_queue)
        : isolate_(isolate),
          incoming_queue_(incoming_queue),
          outgoing_queue_(outcoming_queue) {}

    void Run(JobDelegate* delegate) override {
153 154 155
      LocalIsolate local_isolate(isolate_, ThreadKind::kBackground);
      UnparkedScope unparked_scope(&local_isolate);
      LocalHandleScope handle_scope(&local_isolate);
156 157 158 159 160

      // Since we're going to compile an entire batch, this guarantees that
      // we only switch back the memory chunks to RX at the end.
      CodePageCollectionMemoryModificationScope batch_alloc(isolate_->heap());

161 162
      while (!incoming_queue_->IsEmpty() && !delegate->ShouldYield()) {
        std::unique_ptr<BaselineBatchCompilerJob> job;
163 164
        if (!incoming_queue_->Dequeue(&job)) break;
        DCHECK_NOT_NULL(job);
165
        job->Compile(&local_isolate);
166 167 168 169 170 171
        outgoing_queue_->Enqueue(std::move(job));
      }
      isolate_->stack_guard()->RequestInstallBaselineCode();
    }

    size_t GetMaxConcurrency(size_t worker_count) const override {
172 173 174 175
      size_t max_threads = FLAG_concurrent_sparkplug_max_threads;
      if (max_threads > 0) {
        return std::min(max_threads, incoming_queue_->size());
      }
176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194
      return incoming_queue_->size();
    }

   private:
    Isolate* isolate_;
    LockedQueue<std::unique_ptr<BaselineBatchCompilerJob>>* incoming_queue_;
    LockedQueue<std::unique_ptr<BaselineBatchCompilerJob>>* outgoing_queue_;
  };

  explicit ConcurrentBaselineCompiler(Isolate* isolate) : isolate_(isolate) {
    if (FLAG_concurrent_sparkplug) {
      job_handle_ = V8::GetCurrentPlatform()->PostJob(
          TaskPriority::kUserVisible,
          std::make_unique<JobDispatcher>(isolate_, &incoming_queue_,
                                          &outgoing_queue_));
    }
  }

  ~ConcurrentBaselineCompiler() {
195
    if (job_handle_ && job_handle_->IsValid()) {
196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219
      // Wait for the job handle to complete, so that we know the queue
      // pointers are safe.
      job_handle_->Cancel();
    }
  }

  void CompileBatch(Handle<WeakFixedArray> task_queue, int batch_size) {
    DCHECK(FLAG_concurrent_sparkplug);
    RCS_SCOPE(isolate_, RuntimeCallCounterId::kCompileBaseline);
    incoming_queue_.Enqueue(std::make_unique<BaselineBatchCompilerJob>(
        isolate_, task_queue, batch_size));
    job_handle_->NotifyConcurrencyIncrease();
  }

  void InstallBatch() {
    while (!outgoing_queue_.IsEmpty()) {
      std::unique_ptr<BaselineBatchCompilerJob> job;
      outgoing_queue_.Dequeue(&job);
      job->Install(isolate_);
    }
  }

 private:
  Isolate* isolate_;
220
  std::unique_ptr<JobHandle> job_handle_ = nullptr;
221 222 223 224
  LockedQueue<std::unique_ptr<BaselineBatchCompilerJob>> incoming_queue_;
  LockedQueue<std::unique_ptr<BaselineBatchCompilerJob>> outgoing_queue_;
};

225 226 227 228 229
BaselineBatchCompiler::BaselineBatchCompiler(Isolate* isolate)
    : isolate_(isolate),
      compilation_queue_(Handle<WeakFixedArray>::null()),
      last_index_(0),
      estimated_instruction_size_(0),
230 231 232 233 234 235
      enabled_(true) {
  if (FLAG_concurrent_sparkplug) {
    concurrent_compiler_ =
        std::make_unique<ConcurrentBaselineCompiler>(isolate_);
  }
}
236 237 238 239 240 241 242 243

BaselineBatchCompiler::~BaselineBatchCompiler() {
  if (!compilation_queue_.is_null()) {
    GlobalHandles::Destroy(compilation_queue_.location());
    compilation_queue_ = Handle<WeakFixedArray>::null();
  }
}

244
void BaselineBatchCompiler::EnqueueFunction(Handle<JSFunction> function) {
245 246 247
  Handle<SharedFunctionInfo> shared(function->shared(), isolate_);
  // Early return if the function is compiled with baseline already or it is not
  // suitable for baseline compilation.
248 249
  if (shared->HasBaselineCode()) return;
  if (!CanCompileWithBaseline(isolate_, *shared)) return;
250 251 252 253 254

  // Immediately compile the function if batch compilation is disabled.
  if (!is_enabled()) {
    IsCompiledScope is_compiled_scope(
        function->shared().is_compiled_scope(isolate_));
255 256 257
    Compiler::CompileBaseline(isolate_, function, Compiler::CLEAR_EXCEPTION,
                              &is_compiled_scope);
    return;
258 259
  }

260
  int estimated_size;
261 262
  {
    DisallowHeapAllocation no_gc;
263
    estimated_size = BaselineCompiler::EstimateInstructionSize(
264 265
        shared->GetBytecodeArray(isolate_));
  }
266 267 268 269 270 271 272 273 274 275 276
  estimated_instruction_size_ += estimated_size;
  if (FLAG_trace_baseline_batch_compilation) {
    CodeTracer::Scope trace_scope(isolate_->GetCodeTracer());
    PrintF(trace_scope.file(),
           "[Baseline batch compilation] Enqueued function ");
    function->PrintName(trace_scope.file());
    PrintF(trace_scope.file(),
           " with estimated size %d (current budget: %d/%d)\n", estimated_size,
           estimated_instruction_size_,
           FLAG_baseline_batch_compilation_threshold);
  }
277
  if (ShouldCompileBatch()) {
278 279 280 281 282 283 284
    if (FLAG_trace_baseline_batch_compilation) {
      CodeTracer::Scope trace_scope(isolate_->GetCodeTracer());
      PrintF(trace_scope.file(),
             "[Baseline batch compilation] Compiling current batch of %d "
             "functions\n",
             (last_index_ + 1));
    }
285 286
    if (FLAG_concurrent_sparkplug) {
      Enqueue(shared);
287
      concurrent_compiler_->CompileBatch(compilation_queue_, last_index_);
288 289 290 291 292 293
      ClearBatch();
    } else {
      CompileBatch(function);
    }
  } else {
    Enqueue(shared);
294
  }
295 296 297
}

void BaselineBatchCompiler::Enqueue(Handle<SharedFunctionInfo> shared) {
298 299
  EnsureQueueCapacity();
  compilation_queue_->Set(last_index_++, HeapObjectReference::Weak(*shared));
300 301 302 303 304
}

void BaselineBatchCompiler::InstallBatch() {
  DCHECK(FLAG_concurrent_sparkplug);
  concurrent_compiler_->InstallBatch();
305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365
}

void BaselineBatchCompiler::EnsureQueueCapacity() {
  if (compilation_queue_.is_null()) {
    compilation_queue_ = isolate_->global_handles()->Create(
        *isolate_->factory()->NewWeakFixedArray(kInitialQueueSize,
                                                AllocationType::kOld));
    return;
  }
  if (last_index_ >= compilation_queue_->length()) {
    Handle<WeakFixedArray> new_queue =
        isolate_->factory()->CopyWeakFixedArrayAndGrow(compilation_queue_,
                                                       last_index_);
    GlobalHandles::Destroy(compilation_queue_.location());
    compilation_queue_ = isolate_->global_handles()->Create(*new_queue);
  }
}

void BaselineBatchCompiler::CompileBatch(Handle<JSFunction> function) {
  CodePageCollectionMemoryModificationScope batch_allocation(isolate_->heap());
  {
    IsCompiledScope is_compiled_scope(
        function->shared().is_compiled_scope(isolate_));
    Compiler::CompileBaseline(isolate_, function, Compiler::CLEAR_EXCEPTION,
                              &is_compiled_scope);
  }
  for (int i = 0; i < last_index_; i++) {
    MaybeObject maybe_sfi = compilation_queue_->Get(i);
    MaybeCompileFunction(maybe_sfi);
    compilation_queue_->Set(i, HeapObjectReference::ClearedValue(isolate_));
  }
  ClearBatch();
}

bool BaselineBatchCompiler::ShouldCompileBatch() const {
  return estimated_instruction_size_ >=
         FLAG_baseline_batch_compilation_threshold;
}

bool BaselineBatchCompiler::MaybeCompileFunction(MaybeObject maybe_sfi) {
  HeapObject heapobj;
  // Skip functions where the weak reference is no longer valid.
  if (!maybe_sfi.GetHeapObjectIfWeak(&heapobj)) return false;
  Handle<SharedFunctionInfo> shared =
      handle(SharedFunctionInfo::cast(heapobj), isolate_);
  // Skip functions where the bytecode has been flushed.
  if (!shared->is_compiled()) return false;

  IsCompiledScope is_compiled_scope(shared->is_compiled_scope(isolate_));
  return Compiler::CompileSharedWithBaseline(
      isolate_, shared, Compiler::CLEAR_EXCEPTION, &is_compiled_scope);
}

void BaselineBatchCompiler::ClearBatch() {
  estimated_instruction_size_ = 0;
  last_index_ = 0;
}

}  // namespace baseline
}  // namespace internal
}  // namespace v8
366 367 368 369 370 371 372

#else

namespace v8 {
namespace internal {
namespace baseline {

373 374
class ConcurrentBaselineCompiler {};

375 376 377 378 379 380 381 382 383 384 385 386 387 388
BaselineBatchCompiler::BaselineBatchCompiler(Isolate* isolate)
    : isolate_(isolate),
      compilation_queue_(Handle<WeakFixedArray>::null()),
      last_index_(0),
      estimated_instruction_size_(0),
      enabled_(false) {}

BaselineBatchCompiler::~BaselineBatchCompiler() {
  if (!compilation_queue_.is_null()) {
    GlobalHandles::Destroy(compilation_queue_.location());
    compilation_queue_ = Handle<WeakFixedArray>::null();
  }
}

389 390
void BaselineBatchCompiler::InstallBatch() { UNREACHABLE(); }

391 392 393 394 395
}  // namespace baseline
}  // namespace internal
}  // namespace v8

#endif