// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "gin/public/isolate_holder.h"

#include <stddef.h>
#include <stdlib.h>
#include <string.h>

#include <memory>
#include <utility>

#include "base/check_op.h"
#include "base/system/sys_info.h"
#include "base/task/current_thread.h"
#include "base/task/single_thread_task_runner.h"
#include "build/build_config.h"
#include "gin/debug_impl.h"
#include "gin/function_template.h"
#include "gin/per_isolate_data.h"
#include "gin/v8_initializer.h"
#include "gin/v8_isolate_memory_dump_provider.h"
#include "gin/v8_shared_memory_dump_provider.h"
#include "v8/include/v8-isolate.h"
#include "v8/include/v8-snapshot.h"

namespace gin {

namespace {
v8::ArrayBuffer::Allocator* g_array_buffer_allocator = nullptr;
const intptr_t* g_reference_table = nullptr;
v8::FatalErrorCallback g_fatal_error_callback = nullptr;
v8::OOMErrorCallback g_oom_error_callback = nullptr;

std::unique_ptr<v8::Isolate::CreateParams> getModifiedIsolateParams(
    std::unique_ptr<v8::Isolate::CreateParams> params,
    IsolateHolder::AllowAtomicsWaitMode atomics_wait_mode,
    v8::CreateHistogramCallback create_histogram_callback,
    v8::AddHistogramSampleCallback add_histogram_sample_callback) {
  params->create_histogram_callback = create_histogram_callback;
  params->add_histogram_sample_callback = add_histogram_sample_callback;
  params->allow_atomics_wait =
      atomics_wait_mode ==
      IsolateHolder::AllowAtomicsWaitMode::kAllowAtomicsWait;
  params->array_buffer_allocator = g_array_buffer_allocator;
  return params;
}
}  // namespace

IsolateHolder::IsolateHolder(
    scoped_refptr<base::SingleThreadTaskRunner> task_runner,
    IsolateType isolate_type)
    : IsolateHolder(std::move(task_runner),
                    AccessMode::kSingleThread,
                    isolate_type) {}

IsolateHolder::IsolateHolder(
    scoped_refptr<base::SingleThreadTaskRunner> task_runner,
    AccessMode access_mode,
    IsolateType isolate_type)
    : IsolateHolder(std::move(task_runner),
                    access_mode,
                    kAllowAtomicsWait,
                    isolate_type,
                    IsolateCreationMode::kNormal) {}

IsolateHolder::IsolateHolder(
    scoped_refptr<base::SingleThreadTaskRunner> task_runner,
    AccessMode access_mode,
    AllowAtomicsWaitMode atomics_wait_mode,
    IsolateType isolate_type,
    IsolateCreationMode isolate_creation_mode,
    v8::CreateHistogramCallback create_histogram_callback,
    v8::AddHistogramSampleCallback add_histogram_sample_callback)
    : IsolateHolder(task_runner,
                    access_mode,
                    isolate_type,
                    getModifiedIsolateParams(getDefaultIsolateParams(),
                                             atomics_wait_mode,
                                             create_histogram_callback,
                                             add_histogram_sample_callback),
                    isolate_creation_mode) {}

IsolateHolder::IsolateHolder(
    scoped_refptr<base::SingleThreadTaskRunner> task_runner,
    AccessMode access_mode,
    IsolateType isolate_type,
    std::unique_ptr<v8::Isolate::CreateParams> params,
    IsolateCreationMode isolate_creation_mode)
    : access_mode_(access_mode), isolate_type_(isolate_type) {
  CHECK(Initialized())
      << "You need to invoke gin::IsolateHolder::Initialize first";

  DCHECK(task_runner);
  DCHECK(task_runner->BelongsToCurrentThread());

  v8::ArrayBuffer::Allocator* allocator = params->array_buffer_allocator;
  DCHECK(allocator);

  isolate_ = v8::Isolate::Allocate();
  isolate_data_ = std::make_unique<PerIsolateData>(isolate_, allocator,
                                                   access_mode_, task_runner);
  //  TODO(https://crbug.com/1347092): Refactor such that caller need not
  //  provide params when creating a snapshot.
  if (isolate_creation_mode == IsolateCreationMode::kCreateSnapshot) {
    // This branch is called when creating a V8 snapshot for Blink.
    // Note SnapshotCreator calls isolate->Enter() in its construction.
    snapshot_creator_ =
        std::make_unique<v8::SnapshotCreator>(isolate_, g_reference_table);
    DCHECK_EQ(isolate_, snapshot_creator_->GetIsolate());
  } else {
    v8::Isolate::Initialize(isolate_, *params);
  }

  // This will attempt register the shared memory dump provider for every
  // IsolateHolder, but only the first registration will have any effect.
  gin::V8SharedMemoryDumpProvider::Register();

  isolate_memory_dump_provider_ =
      std::make_unique<V8IsolateMemoryDumpProvider>(this, task_runner);
}

IsolateHolder::~IsolateHolder() {
  isolate_memory_dump_provider_.reset();
  // Calling Isolate::Dispose makes sure all threads which might access
  // PerIsolateData are finished.
  isolate_->Dispose();
  isolate_data_.reset();
  isolate_ = nullptr;
}

// static
void IsolateHolder::Initialize(ScriptMode mode,
                               v8::ArrayBuffer::Allocator* allocator,
                               const intptr_t* reference_table,
                               const std::string js_command_line_flags,
                               v8::FatalErrorCallback fatal_error_callback,
                               v8::OOMErrorCallback oom_error_callback) {
  CHECK(allocator);
  V8Initializer::Initialize(mode, js_command_line_flags, oom_error_callback);
  g_array_buffer_allocator = allocator;
  g_reference_table = reference_table;
  g_fatal_error_callback = fatal_error_callback;
  g_oom_error_callback = oom_error_callback;
}

// static
bool IsolateHolder::Initialized() {
  return g_array_buffer_allocator;
}

// static
std::unique_ptr<v8::Isolate::CreateParams>
IsolateHolder::getDefaultIsolateParams() {
  CHECK(Initialized())
      << "You need to invoke gin::IsolateHolder::Initialize first";
  // TODO(https://crbug.com/v8/13092): Remove usage of unique_ptr once V8
  // supports the move constructor on CreateParams.
  std::unique_ptr<v8::Isolate::CreateParams> params =
      std::make_unique<v8::Isolate::CreateParams>();
  params->code_event_handler = DebugImpl::GetJitCodeEventHandler();
  params->constraints.ConfigureDefaults(base::SysInfo::AmountOfPhysicalMemory(),
                                        base::SysInfo::AmountOfVirtualMemory());
  params->array_buffer_allocator = g_array_buffer_allocator;
  params->allow_atomics_wait = true;
  params->external_references = g_reference_table;
  params->only_terminate_in_safe_scope = true;
  params->embedder_wrapper_type_index = kWrapperInfoIndex;
  params->embedder_wrapper_object_index = kEncodedValueIndex;
  params->fatal_error_callback = g_fatal_error_callback;
  params->oom_error_callback = g_oom_error_callback;
  return params;
}

void IsolateHolder::EnableIdleTasks(
    std::unique_ptr<V8IdleTaskRunner> idle_task_runner) {
  DCHECK(isolate_data_.get());
  isolate_data_->EnableIdleTasks(std::move(idle_task_runner));
}

}  // namespace gin