// 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/asmjs/asm-js.h"

#include "src/api-natives.h"
#include "src/api.h"
#include "src/asmjs/asm-typer.h"
#include "src/asmjs/asm-wasm-builder.h"
#include "src/assert-scope.h"
#include "src/execution.h"
#include "src/factory.h"
#include "src/handles.h"
#include "src/isolate.h"
#include "src/objects.h"
#include "src/parsing/parse-info.h"

#include "src/wasm/encoder.h"
#include "src/wasm/module-decoder.h"
#include "src/wasm/wasm-js.h"
#include "src/wasm/wasm-module.h"
#include "src/wasm/wasm-result.h"

typedef uint8_t byte;

using v8::internal::wasm::ErrorThrower;

namespace v8 {
namespace internal {

namespace {
i::MaybeHandle<i::FixedArray> CompileModule(
    i::Isolate* isolate, const byte* start, const byte* end,
    ErrorThrower* thrower,
    internal::wasm::ModuleOrigin origin = i::wasm::kWasmOrigin) {
  // Decode but avoid a redundant pass over function bodies for verification.
  // Verification will happen during compilation.
  i::Zone zone(isolate->allocator());
  internal::wasm::ModuleResult result = internal::wasm::DecodeWasmModule(
      isolate, &zone, start, end, false, origin);

  i::MaybeHandle<i::FixedArray> compiled_module;
  if (result.failed() && origin == internal::wasm::kAsmJsOrigin) {
    thrower->Error("Asm.js converted module failed to decode");
  } else if (result.failed()) {
    thrower->Failed("", result);
  } else {
    compiled_module = result.val->CompileFunctions(isolate, thrower);
  }

  if (result.val) delete result.val;
  return compiled_module;
}

Handle<i::Object> StdlibMathMember(i::Isolate* isolate,
                                   Handle<JSReceiver> stdlib,
                                   Handle<Name> name) {
  if (stdlib.is_null()) {
    return Handle<i::Object>();
  }
  Handle<i::Name> math_name(
      isolate->factory()->InternalizeOneByteString(STATIC_CHAR_VECTOR("Math")));
  MaybeHandle<i::Object> maybe_math = i::Object::GetProperty(stdlib, math_name);
  if (maybe_math.is_null()) {
    return Handle<i::Object>();
  }
  Handle<i::Object> math = maybe_math.ToHandleChecked();
  if (!math->IsJSReceiver()) {
    return Handle<i::Object>();
  }
  MaybeHandle<i::Object> maybe_value = i::Object::GetProperty(math, name);
  if (maybe_value.is_null()) {
    return Handle<i::Object>();
  }
  return maybe_value.ToHandleChecked();
}

bool IsStdlibMemberValid(i::Isolate* isolate, Handle<JSReceiver> stdlib,
                         Handle<i::Object> member_id) {
  int32_t member_kind;
  if (!member_id->ToInt32(&member_kind)) {
    UNREACHABLE();
  }
  switch (member_kind) {
    case wasm::AsmTyper::StandardMember::kNone:
    case wasm::AsmTyper::StandardMember::kModule:
    case wasm::AsmTyper::StandardMember::kStdlib:
    case wasm::AsmTyper::StandardMember::kHeap:
    case wasm::AsmTyper::StandardMember::kFFI: {
      // Nothing to check for these.
      return true;
    }
    case wasm::AsmTyper::StandardMember::kInfinity: {
      if (stdlib.is_null()) {
        return false;
      }
      Handle<i::Name> name(isolate->factory()->InternalizeOneByteString(
          STATIC_CHAR_VECTOR("Infinity")));
      MaybeHandle<i::Object> maybe_value = i::Object::GetProperty(stdlib, name);
      if (maybe_value.is_null()) {
        return false;
      }
      Handle<i::Object> value = maybe_value.ToHandleChecked();
      return value->IsNumber() && std::isinf(value->Number());
    }
    case wasm::AsmTyper::StandardMember::kNaN: {
      if (stdlib.is_null()) {
        return false;
      }
      Handle<i::Name> name(isolate->factory()->InternalizeOneByteString(
          STATIC_CHAR_VECTOR("NaN")));
      MaybeHandle<i::Object> maybe_value = i::Object::GetProperty(stdlib, name);
      if (maybe_value.is_null()) {
        return false;
      }
      Handle<i::Object> value = maybe_value.ToHandleChecked();
      return value->IsNaN();
    }
#define STDLIB_MATH_FUNC(CamelName, fname)                             \
  case wasm::AsmTyper::StandardMember::k##CamelName: {                 \
    Handle<i::Name> name(isolate->factory()->InternalizeOneByteString( \
        STATIC_CHAR_VECTOR(#fname)));                                  \
    Handle<i::Object> value = StdlibMathMember(isolate, stdlib, name); \
    if (value.is_null() || !value->IsJSFunction()) {                   \
      return false;                                                    \
    }                                                                  \
    Handle<i::JSFunction> func(i::JSFunction::cast(*value));           \
    return func->shared()->code() ==                                   \
           isolate->builtins()->builtin(Builtins::k##CamelName);       \
  }
      STDLIB_MATH_FUNC(MathAcos, acos)
      STDLIB_MATH_FUNC(MathAsin, asin)
      STDLIB_MATH_FUNC(MathAtan, atan)
      STDLIB_MATH_FUNC(MathCos, cos)
      STDLIB_MATH_FUNC(MathSin, sin)
      STDLIB_MATH_FUNC(MathTan, tan)
      STDLIB_MATH_FUNC(MathExp, exp)
      STDLIB_MATH_FUNC(MathLog, log)
      STDLIB_MATH_FUNC(MathCeil, ceil)
      STDLIB_MATH_FUNC(MathFloor, floor)
      STDLIB_MATH_FUNC(MathSqrt, sqrt)
      STDLIB_MATH_FUNC(MathAbs, abs)
      STDLIB_MATH_FUNC(MathClz32, clz32)
      STDLIB_MATH_FUNC(MathMin, min)
      STDLIB_MATH_FUNC(MathMax, max)
      STDLIB_MATH_FUNC(MathAtan2, atan2)
      STDLIB_MATH_FUNC(MathPow, pow)
      STDLIB_MATH_FUNC(MathImul, imul)
      STDLIB_MATH_FUNC(MathFround, fround)
#undef STDLIB_MATH_FUNC
#define STDLIB_MATH_CONST(cname, const_value)                             \
  case wasm::AsmTyper::StandardMember::kMath##cname: {                    \
    i::Handle<i::Name> name(isolate->factory()->InternalizeOneByteString( \
        STATIC_CHAR_VECTOR(#cname)));                                     \
    i::Handle<i::Object> value = StdlibMathMember(isolate, stdlib, name); \
    return !value.is_null() && value->IsNumber() &&                       \
           value->Number() == const_value;                                \
  }
      STDLIB_MATH_CONST(E, 2.718281828459045)
      STDLIB_MATH_CONST(LN10, 2.302585092994046)
      STDLIB_MATH_CONST(LN2, 0.6931471805599453)
      STDLIB_MATH_CONST(LOG2E, 1.4426950408889634)
      STDLIB_MATH_CONST(LOG10E, 0.4342944819032518)
      STDLIB_MATH_CONST(PI, 3.141592653589793)
      STDLIB_MATH_CONST(SQRT1_2, 0.7071067811865476)
      STDLIB_MATH_CONST(SQRT2, 1.4142135623730951)
#undef STDLIB_MATH_CONST
    default: { UNREACHABLE(); }
  }
  return false;
}

}  // namespace

MaybeHandle<FixedArray> AsmJs::ConvertAsmToWasm(ParseInfo* info) {
  ErrorThrower thrower(info->isolate(), "Asm.js -> WebAssembly conversion");
  wasm::AsmTyper typer(info->isolate(), info->zone(), *(info->script()),
                       info->literal());
  if (!typer.Validate()) {
    DCHECK(!info->isolate()->has_pending_exception());
    PrintF("Validation of asm.js module failed: %s", typer.error_message());
    return MaybeHandle<FixedArray>();
  }
  v8::internal::wasm::AsmWasmBuilder builder(info->isolate(), info->zone(),
                                             info->literal(), &typer);
  i::Handle<i::FixedArray> foreign_globals;
  auto module = builder.Run(&foreign_globals);

  i::MaybeHandle<i::FixedArray> compiled =
      CompileModule(info->isolate(), module->begin(), module->end(), &thrower,
                    internal::wasm::kAsmJsOrigin);
  DCHECK(!compiled.is_null());

  wasm::AsmTyper::StdlibSet uses = typer.StdlibUses();
  Handle<FixedArray> uses_array =
      info->isolate()->factory()->NewFixedArray(static_cast<int>(uses.size()));
  int count = 0;
  for (auto i : uses) {
    uses_array->set(count++, Smi::FromInt(i));
  }

  Handle<FixedArray> result = info->isolate()->factory()->NewFixedArray(3);
  result->set(0, *compiled.ToHandleChecked());
  result->set(1, *foreign_globals);
  result->set(2, *uses_array);
  return result;
}

bool AsmJs::IsStdlibValid(i::Isolate* isolate, Handle<FixedArray> wasm_data,
                          Handle<JSReceiver> stdlib) {
  i::Handle<i::FixedArray> uses(i::FixedArray::cast(wasm_data->get(2)));
  for (int i = 0; i < uses->length(); ++i) {
    if (!IsStdlibMemberValid(isolate, stdlib,
                             uses->GetValueChecked<i::Object>(isolate, i))) {
      return false;
    }
  }
  return true;
}

MaybeHandle<Object> AsmJs::InstantiateAsmWasm(i::Isolate* isolate,
                                              Handle<FixedArray> wasm_data,
                                              Handle<JSArrayBuffer> memory,
                                              Handle<JSReceiver> foreign) {
  i::Handle<i::FixedArray> compiled(i::FixedArray::cast(wasm_data->get(0)));
  i::Handle<i::FixedArray> foreign_globals(
      i::FixedArray::cast(wasm_data->get(1)));

  ErrorThrower thrower(isolate, "Asm.js -> WebAssembly instantiation");

  i::MaybeHandle<i::JSObject> maybe_module_object =
      i::wasm::WasmModule::Instantiate(isolate, compiled, foreign, memory);
  if (maybe_module_object.is_null()) {
    return MaybeHandle<Object>();
  }

  i::Handle<i::Name> name(isolate->factory()->InternalizeOneByteString(
      STATIC_CHAR_VECTOR("__foreign_init__")));

  i::Handle<i::Object> module_object = maybe_module_object.ToHandleChecked();
  i::MaybeHandle<i::Object> maybe_init =
      i::Object::GetProperty(module_object, name);
  DCHECK(!maybe_init.is_null());

  i::Handle<i::Object> init = maybe_init.ToHandleChecked();
  i::Handle<i::Object> undefined(isolate->heap()->undefined_value(), isolate);
  i::Handle<i::Object>* foreign_args_array =
      new i::Handle<i::Object>[foreign_globals->length()];
  for (int j = 0; j < foreign_globals->length(); j++) {
    if (!foreign.is_null()) {
      i::MaybeHandle<i::Name> name = i::Object::ToName(
          isolate, i::Handle<i::Object>(foreign_globals->get(j), isolate));
      if (!name.is_null()) {
        i::MaybeHandle<i::Object> val =
            i::Object::GetProperty(foreign, name.ToHandleChecked());
        if (!val.is_null()) {
          foreign_args_array[j] = val.ToHandleChecked();
          continue;
        }
      }
    }
    foreign_args_array[j] = undefined;
  }
  i::MaybeHandle<i::Object> retval = i::Execution::Call(
      isolate, init, undefined, foreign_globals->length(), foreign_args_array);
  delete[] foreign_args_array;

  DCHECK(!retval.is_null());

  return maybe_module_object;
}

}  // namespace internal
}  // namespace v8
