// Copyright 2019 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 "test/wasm-api-tests/wasm-api-test.h"

namespace v8 {
namespace internal {
namespace wasm {

namespace {

int g_instances_finalized = 0;
int g_functions_finalized = 0;
int g_foreigns_finalized = 0;
int g_modules_finalized = 0;

const int kModuleMagic = 42;

void FinalizeInstance(void* data) {
  int iteration = static_cast<int>(reinterpret_cast<intptr_t>(data));
  g_instances_finalized += iteration;
}

void FinalizeFunction(void* data) {
  int iteration = static_cast<int>(reinterpret_cast<intptr_t>(data));
  g_functions_finalized += iteration;
}

void FinalizeForeign(void* data) {
  int iteration = static_cast<int>(reinterpret_cast<intptr_t>(data));
  g_foreigns_finalized += iteration;
}

void FinalizeModule(void* data) {
  g_modules_finalized += static_cast<int>(reinterpret_cast<intptr_t>(data));
}

void RunInStore(Store* store, ZoneBuffer* wire_bytes, int iterations) {
  size_t size = wire_bytes->end() - wire_bytes->begin();
  vec<byte_t> binary = vec<byte_t>::make(
      size, reinterpret_cast<byte_t*>(const_cast<byte*>(wire_bytes->begin())));
  own<Module> module = Module::make(store, binary);
  module->set_host_info(reinterpret_cast<void*>(kModuleMagic), &FinalizeModule);
  for (int iteration = 0; iteration < iterations; iteration++) {
    void* finalizer_data = reinterpret_cast<void*>(iteration);
    own<Instance> instance = Instance::make(store, module.get(), nullptr);
    EXPECT_NE(nullptr, instance.get());
    instance->set_host_info(finalizer_data, &FinalizeInstance);

    own<Func> func = instance->exports()[0]->func()->copy();
    ASSERT_NE(func, nullptr);
    func->set_host_info(finalizer_data, &FinalizeFunction);
    Val args[] = {Val::i32(iteration)};
    Val results[1];
    func->call(args, results);
    EXPECT_EQ(iteration, results[0].i32());

    own<Foreign> foreign = Foreign::make(store);
    foreign->set_host_info(finalizer_data, &FinalizeForeign);
  }
}

}  // namespace

TEST_F(WasmCapiTest, InstanceFinalization) {
  // Add a dummy function: f(x) { return x; }
  byte code[] = {WASM_RETURN1(WASM_LOCAL_GET(0))};
  AddExportedFunction(CStrVector("f"), code, sizeof(code), wasm_i_i_sig());
  Compile();
  g_instances_finalized = 0;
  g_functions_finalized = 0;
  g_foreigns_finalized = 0;
  g_modules_finalized = 0;
  module()->set_host_info(reinterpret_cast<void*>(kModuleMagic),
                          &FinalizeModule);
  static const int kIterations = 10;
  RunInStore(store(), wire_bytes(), kIterations);
  {
    own<Store> store2 = Store::make(engine());
    RunInStore(store2.get(), wire_bytes(), kIterations);
  }
  RunInStore(store(), wire_bytes(), kIterations);
  Shutdown();
  // Verify that (1) all finalizers were called, and (2) they passed the
  // correct host data: the loop above sets {i} as data, and the finalizer
  // callbacks add them all up, so the expected value after three rounds is
  // 3 * sum([0, 1, ..., kIterations - 1]), which per Gauss's formula is:
  static const int kExpected = 3 * ((kIterations * (kIterations - 1)) / 2);
  EXPECT_EQ(g_instances_finalized, kExpected);
  // There are two functions per iteration.
  EXPECT_EQ(g_functions_finalized, kExpected);
  EXPECT_EQ(g_foreigns_finalized, kExpected);
  EXPECT_EQ(g_modules_finalized, 4 * kModuleMagic);
}

namespace {

own<Trap> CapiFunction(void* env, const Val args[], Val results[]) {
  int offset = static_cast<int>(reinterpret_cast<intptr_t>(env));
  results[0] = Val::i32(offset + args[0].i32());
  return nullptr;
}

int g_host_data_finalized = 0;
int g_capi_function_finalized = 0;

void FinalizeCapiFunction(void* data) {
  int value = static_cast<int>(reinterpret_cast<intptr_t>(data));
  g_capi_function_finalized += value;
}

void FinalizeHostData(void* data) {
  g_host_data_finalized += static_cast<int>(reinterpret_cast<intptr_t>(data));
}

}  // namespace

TEST_F(WasmCapiTest, CapiFunctionLifetimes) {
  uint32_t func_index = builder()->AddImport(CStrVector("f"), wasm_i_i_sig());
  builder()->ExportImportedFunction(CStrVector("f"), func_index);
  Compile();
  own<Instance> instance;
  void* kHostData = reinterpret_cast<void*>(1234);
  int base_summand = 1000;
  {
    // Test that the own<> pointers for Func and FuncType can go out of scope
    // without affecting the ability of the Func to be called later.
    own<FuncType> capi_func_type =
        FuncType::make(ownvec<ValType>::make(ValType::make(::wasm::I32)),
                       ownvec<ValType>::make(ValType::make(::wasm::I32)));
    own<Func> capi_func =
        Func::make(store(), capi_func_type.get(), &CapiFunction,
                   reinterpret_cast<void*>(base_summand));
    Extern* imports[] = {capi_func.get()};
    instance = Instance::make(store(), module(), imports);
    // TODO(jkummerow): It may or may not be desirable to be able to set
    // host data even here and have it survive the import/export dance.
    // We are awaiting resolution of the discussion at:
    // https://github.com/WebAssembly/wasm-c-api/issues/100
  }
  {
    ownvec<Extern> exports = instance->exports();
    Func* exported_func = exports[0]->func();
    constexpr int kArg = 123;
    Val args[] = {Val::i32(kArg)};
    Val results[1];
    exported_func->call(args, results);
    EXPECT_EQ(base_summand + kArg, results[0].i32());
    // Host data should survive destruction of the own<> pointer.
    exported_func->set_host_info(kHostData);
  }
  {
    ownvec<Extern> exports = instance->exports();
    Func* exported_func = exports[0]->func();
    EXPECT_EQ(kHostData, exported_func->get_host_info());
  }
  // Test that a Func can have its own internal metadata, an {env}, and
  // separate {host info}, without any of that interfering with each other.
  g_host_data_finalized = 0;
  g_capi_function_finalized = 0;
  base_summand = 23;
  constexpr int kFinalizerData = 345;
  {
    own<FuncType> capi_func_type =
        FuncType::make(ownvec<ValType>::make(ValType::make(::wasm::I32)),
                       ownvec<ValType>::make(ValType::make(::wasm::I32)));
    own<Func> capi_func = Func::make(
        store(), capi_func_type.get(), &CapiFunction,
        reinterpret_cast<void*>(base_summand), &FinalizeCapiFunction);
    capi_func->set_host_info(reinterpret_cast<void*>(kFinalizerData),
                             &FinalizeHostData);
    constexpr int kArg = 19;
    Val args[] = {Val::i32(kArg)};
    Val results[1];
    capi_func->call(args, results);
    EXPECT_EQ(base_summand + kArg, results[0].i32());
  }
  instance.reset();
  Shutdown();
  EXPECT_EQ(base_summand, g_capi_function_finalized);
  EXPECT_EQ(kFinalizerData, g_host_data_finalized);
}

}  // namespace wasm
}  // namespace internal
}  // namespace v8
