// Copyright 2016 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/common/wasm/wasm-module-runner.h"

#include "src/handles.h"
#include "src/isolate.h"
#include "src/objects.h"
#include "src/property-descriptor.h"
#include "src/wasm/module-decoder.h"
#include "src/wasm/wasm-interpreter.h"
#include "src/wasm/wasm-js.h"
#include "src/wasm/wasm-module.h"
#include "src/wasm/wasm-objects.h"
#include "src/wasm/wasm-result.h"

namespace v8 {
namespace internal {
namespace wasm {
namespace testing {

uint32_t GetMinModuleMemSize(const WasmModule* module) {
  return WasmModule::kPageSize * module->min_mem_pages;
}

const WasmModule* DecodeWasmModuleForTesting(
    Isolate* isolate, ErrorThrower* thrower, const byte* module_start,
    const byte* module_end, ModuleOrigin origin, bool verify_functions) {
  // Decode the module, but don't verify function bodies, since we'll
  // be compiling them anyway.
  ModuleResult decoding_result = DecodeWasmModule(
      isolate, module_start, module_end, verify_functions, origin);

  if (decoding_result.failed()) {
    // Module verification failed. throw.
    thrower->CompileError("WASM.compileRun() failed: %s",
                          decoding_result.error_msg.get());
  }

  if (thrower->error()) {
    if (decoding_result.val) delete decoding_result.val;
    return nullptr;
  }
  return decoding_result.val;
}

const Handle<WasmInstanceObject> InstantiateModuleForTesting(
    Isolate* isolate, ErrorThrower* thrower, const WasmModule* module) {
  CHECK(module != nullptr);

  if (module->import_table.size() > 0) {
    thrower->CompileError("Not supported: module has imports.");
  }

  if (thrower->error()) return Handle<WasmInstanceObject>::null();

  // Although we decoded the module for some pre-validation, run the bytes
  // again through the normal pipeline.
  // TODO(wasm): Use {module} instead of decoding the module bytes again.
  MaybeHandle<WasmModuleObject> module_object = CreateModuleObjectFromBytes(
      isolate, module->module_start, module->module_end, thrower,
      ModuleOrigin::kWasmOrigin, Handle<Script>::null(), nullptr, nullptr);
  if (module_object.is_null()) {
    thrower->CompileError("Module pre-validation failed.");
    return Handle<WasmInstanceObject>::null();
  }
  MaybeHandle<WasmInstanceObject> maybe_instance = WasmModule::Instantiate(
      isolate, thrower, module_object.ToHandleChecked(),
      Handle<JSReceiver>::null(), Handle<JSArrayBuffer>::null());
  Handle<WasmInstanceObject> instance;
  if (!maybe_instance.ToHandle(&instance)) {
    return Handle<WasmInstanceObject>::null();
  }
  return instance;
}

const Handle<WasmInstanceObject> CompileInstantiateWasmModuleForTesting(
    Isolate* isolate, ErrorThrower* thrower, const byte* module_start,
    const byte* module_end, ModuleOrigin origin) {
  std::unique_ptr<const WasmModule> module(DecodeWasmModuleForTesting(
      isolate, thrower, module_start, module_end, origin));

  if (module == nullptr) {
    thrower->CompileError("Wasm module decoding failed");
    return Handle<WasmInstanceObject>::null();
  }
  return InstantiateModuleForTesting(isolate, thrower, module.get());
}

int32_t RunWasmModuleForTesting(Isolate* isolate, Handle<JSObject> instance,
                                int argc, Handle<Object> argv[],
                                ModuleOrigin origin) {
  ErrorThrower thrower(isolate, "RunWasmModule");
  const char* f_name = origin == ModuleOrigin::kAsmJsOrigin ? "caller" : "main";
  return CallWasmFunctionForTesting(isolate, instance, &thrower, f_name, argc,
                                    argv, origin);
}

int32_t CompileAndRunWasmModule(Isolate* isolate, const byte* module_start,
                                const byte* module_end, ModuleOrigin origin) {
  HandleScope scope(isolate);
  ErrorThrower thrower(isolate, "CompileAndRunWasmModule");
  Handle<JSObject> instance = CompileInstantiateWasmModuleForTesting(
      isolate, &thrower, module_start, module_end, origin);
  if (instance.is_null()) {
    return -1;
  }
  return RunWasmModuleForTesting(isolate, instance, 0, nullptr, origin);
}

int32_t InterpretWasmModule(Isolate* isolate, ErrorThrower* thrower,
                            const WasmModule* module, int function_index,
                            WasmVal* args, bool* possible_nondeterminism) {
  CHECK(module != nullptr);

  Zone zone(isolate->allocator(), ZONE_NAME);
  v8::internal::HandleScope scope(isolate);

  if (module->import_table.size() > 0) {
    thrower->CompileError("Not supported: module has imports.");
  }
  if (module->export_table.size() == 0) {
    thrower->CompileError("Not supported: module has no exports.");
  }

  if (thrower->error()) return -1;

  // The code verifies, we create an instance to run it in the interpreter.
  WasmInstance instance(module);
  instance.context = isolate->native_context();
  instance.mem_size = GetMinModuleMemSize(module);
  // TODO(ahaas): Move memory allocation to wasm-module.cc for better
  // encapsulation.
  instance.mem_start =
      static_cast<byte*>(calloc(GetMinModuleMemSize(module), 1));
  instance.globals_start = nullptr;

  WasmInterpreter interpreter(&instance, isolate->allocator());

  WasmInterpreter::Thread* thread = interpreter.GetThread(0);
  thread->Reset();
  thread->PushFrame(&(module->functions[function_index]), args);
  WasmInterpreter::State interpreter_result = thread->Run();
  if (instance.mem_start) {
    free(instance.mem_start);
  }
  *possible_nondeterminism = thread->PossibleNondeterminism();
  if (interpreter_result == WasmInterpreter::FINISHED) {
    WasmVal val = thread->GetReturnValue();
    return val.to<int32_t>();
  } else if (thread->state() == WasmInterpreter::TRAPPED) {
    return 0xdeadbeef;
  } else {
    thrower->RangeError(
        "Interpreter did not finish execution within its step bound");
    return -1;
  }
}

int32_t CallWasmFunctionForTesting(Isolate* isolate, Handle<JSObject> instance,
                                   ErrorThrower* thrower, const char* name,
                                   int argc, Handle<Object> argv[],
                                   ModuleOrigin origin) {
  Handle<JSObject> exports_object;
  if (origin == ModuleOrigin::kAsmJsOrigin) {
    exports_object = instance;
  } else {
    Handle<Name> exports = isolate->factory()->InternalizeUtf8String("exports");
    exports_object = Handle<JSObject>::cast(
        JSObject::GetProperty(instance, exports).ToHandleChecked());
  }
  Handle<Name> main_name = isolate->factory()->NewStringFromAsciiChecked(name);
  PropertyDescriptor desc;
  Maybe<bool> property_found = JSReceiver::GetOwnPropertyDescriptor(
      isolate, exports_object, main_name, &desc);
  if (!property_found.FromMaybe(false)) return -1;

  Handle<JSFunction> main_export = Handle<JSFunction>::cast(desc.value());

  // Call the JS function.
  Handle<Object> undefined = isolate->factory()->undefined_value();
  MaybeHandle<Object> retval =
      Execution::Call(isolate, main_export, undefined, argc, argv);

  // The result should be a number.
  if (retval.is_null()) {
    thrower->RuntimeError("WASM.compileRun() failed: Invocation was null");
    return -1;
  }
  Handle<Object> result = retval.ToHandleChecked();
  if (result->IsSmi()) {
    return Smi::cast(*result)->value();
  }
  if (result->IsHeapNumber()) {
    return static_cast<int32_t>(HeapNumber::cast(*result)->value());
  }
  thrower->RuntimeError(
      "WASM.compileRun() failed: Return value should be number");
  return -1;
}

void SetupIsolateForWasmModule(Isolate* isolate) {
  WasmJs::InstallWasmMapsIfNeeded(isolate, isolate->native_context());
  WasmJs::InstallWasmModuleSymbolIfNeeded(isolate, isolate->global_object(),
                                          isolate->native_context());
}
}  // namespace testing
}  // namespace wasm
}  // namespace internal
}  // namespace v8