// 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-inl.h"
#include "src/objects.h"
#include "src/property-descriptor.h"
#include "src/wasm/module-compiler.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 GetInitialMemSize(const WasmModule* module) {
  return WasmModule::kPageSize * module->initial_pages;
}

std::unique_ptr<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 = SyncDecodeWasmModule(
      isolate, module_start, module_end, verify_functions, origin);

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

  return std::move(decoding_result.val);
}

bool InterpretWasmModuleForTesting(Isolate* isolate,
                                   Handle<WasmInstanceObject> instance,
                                   const char* name, size_t argc,
                                   WasmValue* args) {
  MaybeHandle<WasmExportedFunction> maybe_function =
      GetExportedFunction(isolate, instance, "main");
  Handle<WasmExportedFunction> function;
  if (!maybe_function.ToHandle(&function)) {
    return false;
  }
  int function_index = function->function_index();
  FunctionSig* signature = instance->module()->functions[function_index].sig;
  size_t param_count = signature->parameter_count();
  std::unique_ptr<WasmValue[]> arguments(new WasmValue[param_count]);

  memcpy(arguments.get(), args, std::min(param_count, argc));

  // Fill the parameters up with default values.
  for (size_t i = argc; i < param_count; ++i) {
    switch (signature->GetParam(i)) {
      case MachineRepresentation::kWord32:
        arguments[i] = WasmValue(int32_t{0});
        break;
      case MachineRepresentation::kWord64:
        arguments[i] = WasmValue(int64_t{0});
        break;
      case MachineRepresentation::kFloat32:
        arguments[i] = WasmValue(0.0f);
        break;
      case MachineRepresentation::kFloat64:
        arguments[i] = WasmValue(0.0);
        break;
      default:
        UNREACHABLE();
    }
  }

  // Don't execute more than 16k steps.
  constexpr int kMaxNumSteps = 16 * 1024;

  Zone zone(isolate->allocator(), ZONE_NAME);

  WasmInterpreter* interpreter = WasmDebugInfo::SetupForTesting(instance);
  WasmInterpreter::HeapObjectsScope heap_objects_scope(interpreter, instance);
  WasmInterpreter::Thread* thread = interpreter->GetThread(0);
  thread->Reset();
  thread->InitFrame(&instance->module()->functions[function_index],
                    arguments.get());
  WasmInterpreter::State interpreter_result = thread->Run(kMaxNumSteps);

  return interpreter_result != WasmInterpreter::PAUSED;
}

int32_t RunWasmModuleForTesting(Isolate* isolate,
                                Handle<WasmInstanceObject> instance, int argc,
                                Handle<Object> argv[]) {
  ErrorThrower thrower(isolate, "RunWasmModule");
  return CallWasmFunctionForTesting(isolate, instance, &thrower, "main", argc,
                                    argv);
}

int32_t CompileAndRunWasmModule(Isolate* isolate, const byte* module_start,
                                const byte* module_end) {
  HandleScope scope(isolate);
  ErrorThrower thrower(isolate, "CompileAndRunWasmModule");
  MaybeHandle<WasmInstanceObject> instance = SyncCompileAndInstantiate(
      isolate, &thrower, ModuleWireBytes(module_start, module_end), {}, {});
  if (instance.is_null()) {
    return -1;
  }
  return RunWasmModuleForTesting(isolate, instance.ToHandleChecked(), 0,
                                 nullptr);
}

int32_t CompileAndRunAsmWasmModule(Isolate* isolate, const byte* module_start,
                                   const byte* module_end) {
  HandleScope scope(isolate);
  ErrorThrower thrower(isolate, "CompileAndRunAsmWasmModule");
  MaybeHandle<WasmModuleObject> module = wasm::SyncCompileTranslatedAsmJs(
      isolate, &thrower, ModuleWireBytes(module_start, module_end),
      Handle<Script>::null(), Vector<const byte>());
  DCHECK_EQ(thrower.error(), module.is_null());
  if (module.is_null()) return -1;

  MaybeHandle<WasmInstanceObject> instance = wasm::SyncInstantiate(
      isolate, &thrower, module.ToHandleChecked(), Handle<JSReceiver>::null(),
      Handle<JSArrayBuffer>::null());
  DCHECK_EQ(thrower.error(), instance.is_null());
  if (instance.is_null()) return -1;

  return RunWasmModuleForTesting(isolate, instance.ToHandleChecked(), 0,
                                 nullptr);
}
int32_t InterpretWasmModule(Isolate* isolate,
                            Handle<WasmInstanceObject> instance,
                            ErrorThrower* thrower, int32_t function_index,
                            WasmValue* args, bool* possible_nondeterminism) {
  // Don't execute more than 16k steps.
  constexpr int kMaxNumSteps = 16 * 1024;

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

  WasmInterpreter* interpreter = WasmDebugInfo::SetupForTesting(instance);
  WasmInterpreter::HeapObjectsScope heap_objects_scope(interpreter, instance);
  WasmInterpreter::Thread* thread = interpreter->GetThread(0);
  thread->Reset();
  thread->InitFrame(&(instance->module()->functions[function_index]), args);
  WasmInterpreter::State interpreter_result = thread->Run(kMaxNumSteps);

  *possible_nondeterminism = thread->PossibleNondeterminism();
  if (interpreter_result == WasmInterpreter::FINISHED) {
    WasmValue 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;
  }
}

MaybeHandle<WasmExportedFunction> GetExportedFunction(
    Isolate* isolate, Handle<WasmInstanceObject> instance, const char* name) {
  Handle<JSObject> exports_object;
  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 {};
  if (!desc.value()->IsJSFunction()) return {};

  return Handle<WasmExportedFunction>::cast(desc.value());
}

int32_t CallWasmFunctionForTesting(Isolate* isolate,
                                   Handle<WasmInstanceObject> instance,
                                   ErrorThrower* thrower, const char* name,
                                   int argc, Handle<Object> argv[]) {
  MaybeHandle<WasmExportedFunction> maybe_export =
      GetExportedFunction(isolate, instance, name);
  Handle<WasmExportedFunction> main_export;
  if (!maybe_export.ToHandle(&main_export)) {
    return -1;
  }

  // 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()) {
    DCHECK(isolate->has_pending_exception());
    isolate->clear_pending_exception();
    thrower->RuntimeError("Calling exported wasm function failed.");
    return -1;
  }
  Handle<Object> result = retval.ToHandleChecked();
  if (result->IsSmi()) {
    return Smi::ToInt(*result);
  }
  if (result->IsHeapNumber()) {
    return static_cast<int32_t>(HeapNumber::cast(*result)->value());
  }
  thrower->RuntimeError(
      "Calling exported wasm function failed: Return value should be number");
  return -1;
}

void SetupIsolateForWasmModule(Isolate* isolate) {
  WasmJs::Install(isolate, true);
}

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