// Copyright 2015 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/wasm/wasm-objects.h"
#include "src/wasm/wasm-module.h"

#define TRACE(...)                                      \
  do {                                                  \
    if (FLAG_trace_wasm_instances) PrintF(__VA_ARGS__); \
  } while (false)

#define TRACE_CHAIN(instance)        \
  do {                               \
    instance->PrintInstancesChain(); \
  } while (false)

using namespace v8::internal;
using namespace v8::internal::wasm;

#define DEFINE_ACCESSORS(Container, name, field, type) \
  type* Container::get_##name() {                      \
    return type::cast(GetInternalField(field));        \
  }                                                    \
  void Container::set_##name(type* value) {            \
    return SetInternalField(field, value);             \
  }

#define DEFINE_OPTIONAL_ACCESSORS(Container, name, field, type) \
  bool Container::has_##name() {                                \
    return !GetInternalField(field)->IsUndefined(GetIsolate()); \
  }                                                             \
  type* Container::get_##name() {                               \
    return type::cast(GetInternalField(field));                 \
  }                                                             \
  void Container::set_##name(type* value) {                     \
    return SetInternalField(field, value);                      \
  }

#define DEFINE_GETTER(Container, name, field, type) \
  type* Container::get_##name() { return type::cast(GetInternalField(field)); }

static uint32_t SafeUint32(Object* value) {
  if (value->IsSmi()) {
    int32_t val = Smi::cast(value)->value();
    CHECK_GE(val, 0);
    return static_cast<uint32_t>(val);
  }
  DCHECK(value->IsHeapNumber());
  HeapNumber* num = HeapNumber::cast(value);
  CHECK_GE(num->value(), 0.0);
  CHECK_LE(num->value(), static_cast<double>(kMaxUInt32));
  return static_cast<uint32_t>(num->value());
}

static int32_t SafeInt32(Object* value) {
  if (value->IsSmi()) {
    return Smi::cast(value)->value();
  }
  DCHECK(value->IsHeapNumber());
  HeapNumber* num = HeapNumber::cast(value);
  CHECK_GE(num->value(), static_cast<double>(Smi::kMinValue));
  CHECK_LE(num->value(), static_cast<double>(Smi::kMaxValue));
  return static_cast<int32_t>(num->value());
}

Handle<WasmModuleObject> WasmModuleObject::New(
    Isolate* isolate, Handle<WasmCompiledModule> compiled_module) {
  ModuleOrigin origin = compiled_module->module()->origin;

  Handle<JSObject> module_object;
  if (origin == ModuleOrigin::kWasmOrigin) {
    Handle<JSFunction> module_cons(
        isolate->native_context()->wasm_module_constructor());
    module_object = isolate->factory()->NewJSObject(module_cons);
    Handle<Symbol> module_sym(isolate->native_context()->wasm_module_sym());
    Object::SetProperty(module_object, module_sym, module_object, STRICT)
        .Check();
  } else {
    DCHECK(origin == ModuleOrigin::kAsmJsOrigin);
    Handle<Map> map = isolate->factory()->NewMap(
        JS_OBJECT_TYPE,
        JSObject::kHeaderSize + WasmModuleObject::kFieldCount * kPointerSize);
    module_object = isolate->factory()->NewJSObjectFromMap(map, TENURED);
  }
  module_object->SetInternalField(WasmModuleObject::kCompiledModule,
                                  *compiled_module);
  Handle<WeakCell> link_to_module =
      isolate->factory()->NewWeakCell(module_object);
  compiled_module->set_weak_wasm_module(link_to_module);
  return Handle<WasmModuleObject>::cast(module_object);
}

WasmModuleObject* WasmModuleObject::cast(Object* object) {
  DCHECK(object->IsJSObject());
  // TODO(titzer): brand check for WasmModuleObject.
  return reinterpret_cast<WasmModuleObject*>(object);
}

Handle<WasmTableObject> WasmTableObject::New(Isolate* isolate, uint32_t initial,
                                             uint32_t maximum,
                                             Handle<FixedArray>* js_functions) {
  Handle<JSFunction> table_ctor(
      isolate->native_context()->wasm_table_constructor());
  Handle<JSObject> table_obj = isolate->factory()->NewJSObject(table_ctor);
  *js_functions = isolate->factory()->NewFixedArray(initial);
  Object* null = isolate->heap()->null_value();
  for (int i = 0; i < static_cast<int>(initial); ++i) {
    (*js_functions)->set(i, null);
  }
  table_obj->SetInternalField(kFunctions, *(*js_functions));
  table_obj->SetInternalField(kMaximum,
                              static_cast<Object*>(Smi::FromInt(maximum)));

  Handle<FixedArray> dispatch_tables = isolate->factory()->NewFixedArray(0);
  table_obj->SetInternalField(kDispatchTables, *dispatch_tables);
  Handle<Symbol> table_sym(isolate->native_context()->wasm_table_sym());
  Object::SetProperty(table_obj, table_sym, table_obj, STRICT).Check();
  return Handle<WasmTableObject>::cast(table_obj);
}

DEFINE_GETTER(WasmTableObject, dispatch_tables, kDispatchTables, FixedArray)

Handle<FixedArray> WasmTableObject::AddDispatchTable(
    Isolate* isolate, Handle<WasmTableObject> table_obj,
    Handle<WasmInstanceObject> instance, int table_index,
    Handle<FixedArray> dispatch_table) {
  Handle<FixedArray> dispatch_tables(
      FixedArray::cast(table_obj->GetInternalField(kDispatchTables)), isolate);
  DCHECK_EQ(0, dispatch_tables->length() % 3);

  if (instance.is_null()) return dispatch_tables;
  // TODO(titzer): use weak cells here to avoid leaking instances.

  // Grow the dispatch table and add a new triple at the end.
  Handle<FixedArray> new_dispatch_tables =
      isolate->factory()->CopyFixedArrayAndGrow(dispatch_tables, 3);

  new_dispatch_tables->set(dispatch_tables->length() + 0, *instance);
  new_dispatch_tables->set(dispatch_tables->length() + 1,
                           Smi::FromInt(table_index));
  new_dispatch_tables->set(dispatch_tables->length() + 2, *dispatch_table);

  table_obj->SetInternalField(WasmTableObject::kDispatchTables,
                              *new_dispatch_tables);

  return new_dispatch_tables;
}

DEFINE_ACCESSORS(WasmTableObject, functions, kFunctions, FixedArray)

uint32_t WasmTableObject::current_length() { return get_functions()->length(); }

uint32_t WasmTableObject::maximum_length() {
  return SafeUint32(GetInternalField(kMaximum));
}

WasmTableObject* WasmTableObject::cast(Object* object) {
  DCHECK(object && object->IsJSObject());
  // TODO(titzer): brand check for WasmTableObject.
  return reinterpret_cast<WasmTableObject*>(object);
}

Handle<WasmMemoryObject> WasmMemoryObject::New(Isolate* isolate,
                                               Handle<JSArrayBuffer> buffer,
                                               int maximum) {
  Handle<JSFunction> memory_ctor(
      isolate->native_context()->wasm_memory_constructor());
  Handle<JSObject> memory_obj = isolate->factory()->NewJSObject(memory_ctor);
  memory_obj->SetInternalField(kArrayBuffer, *buffer);
  memory_obj->SetInternalField(kMaximum,
                               static_cast<Object*>(Smi::FromInt(maximum)));
  Handle<Symbol> memory_sym(isolate->native_context()->wasm_memory_sym());
  Object::SetProperty(memory_obj, memory_sym, memory_obj, STRICT).Check();
  return Handle<WasmMemoryObject>::cast(memory_obj);
}

DEFINE_ACCESSORS(WasmMemoryObject, buffer, kArrayBuffer, JSArrayBuffer)

uint32_t WasmMemoryObject::current_pages() {
  return SafeUint32(get_buffer()->byte_length()) / wasm::WasmModule::kPageSize;
}

int32_t WasmMemoryObject::maximum_pages() {
  return SafeInt32(GetInternalField(kMaximum));
}

WasmMemoryObject* WasmMemoryObject::cast(Object* object) {
  DCHECK(object && object->IsJSObject());
  // TODO(titzer): brand check for WasmMemoryObject.
  return reinterpret_cast<WasmMemoryObject*>(object);
}

void WasmMemoryObject::AddInstance(WasmInstanceObject* instance) {
  // TODO(gdeepti): This should be a weak list of instance objects
  // for instances that share memory.
  SetInternalField(kInstance, instance);
}

DEFINE_ACCESSORS(WasmInstanceObject, compiled_module, kCompiledModule,
                 WasmCompiledModule)
DEFINE_OPTIONAL_ACCESSORS(WasmInstanceObject, globals_buffer,
                          kGlobalsArrayBuffer, JSArrayBuffer)
DEFINE_OPTIONAL_ACCESSORS(WasmInstanceObject, memory_buffer, kMemoryArrayBuffer,
                          JSArrayBuffer)
DEFINE_OPTIONAL_ACCESSORS(WasmInstanceObject, memory_object, kMemoryObject,
                          WasmMemoryObject)
DEFINE_OPTIONAL_ACCESSORS(WasmInstanceObject, debug_info, kDebugInfo,
                          WasmDebugInfo)

WasmModuleObject* WasmInstanceObject::module_object() {
  return WasmModuleObject::cast(*get_compiled_module()->wasm_module());
}

WasmModule* WasmInstanceObject::module() {
  return reinterpret_cast<WasmModuleWrapper*>(
             *get_compiled_module()->module_wrapper())
      ->get();
}

WasmInstanceObject* WasmInstanceObject::cast(Object* object) {
  DCHECK(IsWasmInstanceObject(object));
  return reinterpret_cast<WasmInstanceObject*>(object);
}

bool WasmInstanceObject::IsWasmInstanceObject(Object* object) {
  if (!object->IsObject()) return false;
  if (!object->IsJSObject()) return false;

  JSObject* obj = JSObject::cast(object);
  Isolate* isolate = obj->GetIsolate();
  if (obj->GetInternalFieldCount() != kFieldCount) {
    return false;
  }

  Object* mem = obj->GetInternalField(kMemoryArrayBuffer);
  if (!(mem->IsUndefined(isolate) || mem->IsJSArrayBuffer()) ||
      !WasmCompiledModule::IsWasmCompiledModule(
          obj->GetInternalField(kCompiledModule))) {
    return false;
  }

  // All checks passed.
  return true;
}

Handle<WasmInstanceObject> WasmInstanceObject::New(
    Isolate* isolate, Handle<WasmCompiledModule> compiled_module) {
  Handle<Map> map = isolate->factory()->NewMap(
      JS_OBJECT_TYPE, JSObject::kHeaderSize + kFieldCount * kPointerSize);
  Handle<WasmInstanceObject> instance(
      reinterpret_cast<WasmInstanceObject*>(
          *isolate->factory()->NewJSObjectFromMap(map, TENURED)),
      isolate);

  instance->SetInternalField(kCompiledModule, *compiled_module);
  instance->SetInternalField(kMemoryObject, isolate->heap()->undefined_value());
  return instance;
}

WasmInstanceObject* WasmExportedFunction::instance() {
  return WasmInstanceObject::cast(GetInternalField(kInstance));
}

int WasmExportedFunction::function_index() {
  return SafeInt32(GetInternalField(kIndex));
}

WasmExportedFunction* WasmExportedFunction::cast(Object* object) {
  DCHECK(object && object->IsJSFunction());
  DCHECK_EQ(Code::JS_TO_WASM_FUNCTION,
            JSFunction::cast(object)->code()->kind());
  // TODO(titzer): brand check for WasmExportedFunction.
  return reinterpret_cast<WasmExportedFunction*>(object);
}

Handle<WasmExportedFunction> WasmExportedFunction::New(
    Isolate* isolate, Handle<WasmInstanceObject> instance, Handle<String> name,
    Handle<Code> export_wrapper, int arity, int func_index) {
  DCHECK_EQ(Code::JS_TO_WASM_FUNCTION, export_wrapper->kind());
  Handle<SharedFunctionInfo> shared =
      isolate->factory()->NewSharedFunctionInfo(name, export_wrapper, false);
  shared->set_length(arity);
  shared->set_internal_formal_parameter_count(arity);
  Handle<JSFunction> function = isolate->factory()->NewFunction(
      isolate->wasm_function_map(), name, export_wrapper);
  function->set_shared(*shared);

  function->SetInternalField(kInstance, *instance);
  function->SetInternalField(kIndex, Smi::FromInt(func_index));
  return Handle<WasmExportedFunction>::cast(function);
}

Handle<WasmCompiledModule> WasmCompiledModule::New(
    Isolate* isolate, Handle<WasmModuleWrapper> module_wrapper) {
  Handle<FixedArray> ret =
      isolate->factory()->NewFixedArray(PropertyIndices::Count, TENURED);
  // WasmCompiledModule::cast would fail since module bytes are not set yet.
  Handle<WasmCompiledModule> compiled_module(
      reinterpret_cast<WasmCompiledModule*>(*ret), isolate);
  compiled_module->InitId();
  compiled_module->set_module_wrapper(module_wrapper);
  return compiled_module;
}

wasm::WasmModule* WasmCompiledModule::module() const {
  return reinterpret_cast<WasmModuleWrapper*>(*module_wrapper())->get();
}

void WasmCompiledModule::InitId() {
#if DEBUG
  static uint32_t instance_id_counter = 0;
  set(kID_instance_id, Smi::FromInt(instance_id_counter++));
  TRACE("New compiled module id: %d\n", instance_id());
#endif
}

bool WasmCompiledModule::IsWasmCompiledModule(Object* obj) {
  if (!obj->IsFixedArray()) return false;
  FixedArray* arr = FixedArray::cast(obj);
  if (arr->length() != PropertyIndices::Count) return false;
  Isolate* isolate = arr->GetIsolate();
#define WCM_CHECK_SMALL_NUMBER(TYPE, NAME) \
  if (!arr->get(kID_##NAME)->IsSmi()) return false;
#define WCM_CHECK_OBJECT_OR_WEAK(TYPE, NAME)         \
  if (!arr->get(kID_##NAME)->IsUndefined(isolate) && \
      !arr->get(kID_##NAME)->Is##TYPE())             \
    return false;
#define WCM_CHECK_OBJECT(TYPE, NAME) WCM_CHECK_OBJECT_OR_WEAK(TYPE, NAME)
#define WCM_CHECK_WEAK_LINK(TYPE, NAME) WCM_CHECK_OBJECT_OR_WEAK(WeakCell, NAME)
#define WCM_CHECK(KIND, TYPE, NAME) WCM_CHECK_##KIND(TYPE, NAME)
  WCM_PROPERTY_TABLE(WCM_CHECK)
#undef WCM_CHECK

  // All checks passed.
  return true;
}

void WasmCompiledModule::PrintInstancesChain() {
#if DEBUG
  if (!FLAG_trace_wasm_instances) return;
  for (WasmCompiledModule* current = this; current != nullptr;) {
    PrintF("->%d", current->instance_id());
    if (current->ptr_to_weak_next_instance() == nullptr) break;
    CHECK(!current->ptr_to_weak_next_instance()->cleared());
    current =
        WasmCompiledModule::cast(current->ptr_to_weak_next_instance()->value());
  }
  PrintF("\n");
#endif
}

uint32_t WasmCompiledModule::mem_size() const {
  return has_memory() ? memory()->byte_length()->Number() : default_mem_size();
}

uint32_t WasmCompiledModule::default_mem_size() const {
  return min_mem_pages() * WasmModule::kPageSize;
}