// Copyright 2017 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/builtins/builtins-utils-gen.h"
#include "src/codegen/code-stub-assembler.h"
#include "src/codegen/interface-descriptors.h"
#include "src/objects/objects-inl.h"
#include "src/wasm/wasm-objects.h"
#include "src/wasm/wasm-opcodes.h"

namespace v8 {
namespace internal {

class WasmBuiltinsAssembler : public CodeStubAssembler {
 public:
  explicit WasmBuiltinsAssembler(compiler::CodeAssemblerState* state)
      : CodeStubAssembler(state) {}

 protected:
  TNode<WasmInstanceObject> LoadInstanceFromFrame() {
    return CAST(
        LoadFromParentFrame(WasmCompiledFrameConstants::kWasmInstanceOffset));
  }

  TNode<Context> LoadContextFromInstance(TNode<WasmInstanceObject> instance) {
    return CAST(Load(MachineType::AnyTagged(), instance,
                     IntPtrConstant(WasmInstanceObject::kNativeContextOffset -
                                    kHeapObjectTag)));
  }
};

TF_BUILTIN(WasmStackGuard, WasmBuiltinsAssembler) {
  TNode<WasmInstanceObject> instance = LoadInstanceFromFrame();
  TNode<Context> context = LoadContextFromInstance(instance);
  TailCallRuntime(Runtime::kWasmStackGuard, context);
}

TF_BUILTIN(WasmStackOverflow, WasmBuiltinsAssembler) {
  TNode<WasmInstanceObject> instance = LoadInstanceFromFrame();
  TNode<Context> context = LoadContextFromInstance(instance);
  TailCallRuntime(Runtime::kThrowWasmStackOverflow, context);
}

TF_BUILTIN(WasmThrow, WasmBuiltinsAssembler) {
  TNode<Object> exception = CAST(Parameter(Descriptor::kException));
  TNode<WasmInstanceObject> instance = LoadInstanceFromFrame();
  TNode<Context> context = LoadContextFromInstance(instance);
  TailCallRuntime(Runtime::kThrow, context, exception);
}

TF_BUILTIN(WasmRethrow, WasmBuiltinsAssembler) {
  TNode<Object> exception = CAST(Parameter(Descriptor::kException));
  TNode<WasmInstanceObject> instance = LoadInstanceFromFrame();
  TNode<Context> context = LoadContextFromInstance(instance);
  TailCallRuntime(Runtime::kReThrow, context, exception);
}

TF_BUILTIN(WasmTraceMemory, WasmBuiltinsAssembler) {
  TNode<Smi> info = CAST(Parameter(Descriptor::kMemoryTracingInfo));
  TNode<WasmInstanceObject> instance = LoadInstanceFromFrame();
  TNode<Context> context = LoadContextFromInstance(instance);
  TailCallRuntime(Runtime::kWasmTraceMemory, context, info);
}

TF_BUILTIN(WasmAtomicNotify, WasmBuiltinsAssembler) {
  TNode<Uint32T> address =
      UncheckedCast<Uint32T>(Parameter(Descriptor::kAddress));
  TNode<Uint32T> count = UncheckedCast<Uint32T>(Parameter(Descriptor::kCount));

  TNode<WasmInstanceObject> instance = LoadInstanceFromFrame();
  TNode<Number> address_number = ChangeUint32ToTagged(address);
  TNode<Number> count_number = ChangeUint32ToTagged(count);
  TNode<Context> context = LoadContextFromInstance(instance);

  TNode<Smi> result_smi =
      CAST(CallRuntime(Runtime::kWasmAtomicNotify, context, instance,
                       address_number, count_number));
  Return(Unsigned(SmiToInt32(result_smi)));
}

TF_BUILTIN(WasmI32AtomicWait, WasmBuiltinsAssembler) {
  TNode<Uint32T> address =
      UncheckedCast<Uint32T>(Parameter(Descriptor::kAddress));
  TNode<Int32T> expected_value =
      UncheckedCast<Int32T>(Parameter(Descriptor::kExpectedValue));
  TNode<Float64T> timeout =
      UncheckedCast<Float64T>(Parameter(Descriptor::kTimeout));

  TNode<WasmInstanceObject> instance = LoadInstanceFromFrame();
  TNode<Number> address_number = ChangeUint32ToTagged(address);
  TNode<Number> expected_value_number = ChangeInt32ToTagged(expected_value);
  TNode<Number> timeout_number = ChangeFloat64ToTagged(timeout);
  TNode<Context> context = LoadContextFromInstance(instance);

  TNode<Smi> result_smi =
      CAST(CallRuntime(Runtime::kWasmI32AtomicWait, context, instance,
                       address_number, expected_value_number, timeout_number));
  Return(Unsigned(SmiToInt32(result_smi)));
}

TF_BUILTIN(WasmI64AtomicWait, WasmBuiltinsAssembler) {
  TNode<Uint32T> address =
      UncheckedCast<Uint32T>(Parameter(Descriptor::kAddress));
  TNode<Uint32T> expected_value_high =
      UncheckedCast<Uint32T>(Parameter(Descriptor::kExpectedValueHigh));
  TNode<Uint32T> expected_value_low =
      UncheckedCast<Uint32T>(Parameter(Descriptor::kExpectedValueLow));
  TNode<Float64T> timeout =
      UncheckedCast<Float64T>(Parameter(Descriptor::kTimeout));

  TNode<WasmInstanceObject> instance = LoadInstanceFromFrame();
  TNode<Number> address_number = ChangeUint32ToTagged(address);
  TNode<Number> expected_value_high_number =
      ChangeUint32ToTagged(expected_value_high);
  TNode<Number> expected_value_low_number =
      ChangeUint32ToTagged(expected_value_low);
  TNode<Number> timeout_number = ChangeFloat64ToTagged(timeout);
  TNode<Context> context = LoadContextFromInstance(instance);

  TNode<Smi> result_smi = CAST(CallRuntime(
      Runtime::kWasmI64AtomicWait, context, instance, address_number,
      expected_value_high_number, expected_value_low_number, timeout_number));
  Return(Unsigned(SmiToInt32(result_smi)));
}

TF_BUILTIN(WasmMemoryGrow, WasmBuiltinsAssembler) {
  TNode<Int32T> num_pages =
      UncheckedCast<Int32T>(Parameter(Descriptor::kNumPages));
  Label num_pages_out_of_range(this, Label::kDeferred);

  TNode<BoolT> num_pages_fits_in_smi =
      IsValidPositiveSmi(ChangeInt32ToIntPtr(num_pages));
  GotoIfNot(num_pages_fits_in_smi, &num_pages_out_of_range);

  TNode<Smi> num_pages_smi = SmiFromInt32(num_pages);
  TNode<WasmInstanceObject> instance = LoadInstanceFromFrame();
  TNode<Context> context = LoadContextFromInstance(instance);
  TNode<Smi> ret_smi = CAST(
      CallRuntime(Runtime::kWasmMemoryGrow, context, instance, num_pages_smi));
  Return(SmiToInt32(ret_smi));

  BIND(&num_pages_out_of_range);
  Return(Int32Constant(-1));
}

TF_BUILTIN(WasmTableGet, WasmBuiltinsAssembler) {
  TNode<Int32T> entry_index =
      UncheckedCast<Int32T>(Parameter(Descriptor::kEntryIndex));
  TNode<WasmInstanceObject> instance = LoadInstanceFromFrame();
  TNode<Context> context = LoadContextFromInstance(instance);
  Label entry_index_out_of_range(this, Label::kDeferred);

  TNode<BoolT> entry_index_fits_in_smi =
      IsValidPositiveSmi(ChangeInt32ToIntPtr(entry_index));
  GotoIfNot(entry_index_fits_in_smi, &entry_index_out_of_range);

  TNode<Smi> entry_index_smi = SmiFromInt32(entry_index);
  TNode<Smi> table_index_smi = CAST(Parameter(Descriptor::kTableIndex));

  TailCallRuntime(Runtime::kWasmFunctionTableGet, context, instance,
                  table_index_smi, entry_index_smi);

  BIND(&entry_index_out_of_range);
  MessageTemplate message_id =
      wasm::WasmOpcodes::TrapReasonToMessageId(wasm::kTrapTableOutOfBounds);
  TailCallRuntime(Runtime::kThrowWasmError, context,
                  SmiConstant(static_cast<int>(message_id)));
}

TF_BUILTIN(WasmTableSet, WasmBuiltinsAssembler) {
  TNode<Int32T> entry_index =
      UncheckedCast<Int32T>(Parameter(Descriptor::kEntryIndex));
  TNode<WasmInstanceObject> instance = LoadInstanceFromFrame();
  TNode<Context> context = LoadContextFromInstance(instance);
  Label entry_index_out_of_range(this, Label::kDeferred);

  TNode<BoolT> entry_index_fits_in_smi =
      IsValidPositiveSmi(ChangeInt32ToIntPtr(entry_index));
  GotoIfNot(entry_index_fits_in_smi, &entry_index_out_of_range);

  TNode<Smi> entry_index_smi = SmiFromInt32(entry_index);
  TNode<Smi> table_index_smi = CAST(Parameter(Descriptor::kTableIndex));
  TNode<Object> value = CAST(Parameter(Descriptor::kValue));
  TailCallRuntime(Runtime::kWasmFunctionTableSet, context, instance,
                  table_index_smi, entry_index_smi, value);

  BIND(&entry_index_out_of_range);
  MessageTemplate message_id =
      wasm::WasmOpcodes::TrapReasonToMessageId(wasm::kTrapTableOutOfBounds);
  TailCallRuntime(Runtime::kThrowWasmError, context,
                  SmiConstant(static_cast<int>(message_id)));
}

#define DECLARE_THROW_RUNTIME_FN(name)                            \
  TF_BUILTIN(ThrowWasm##name, WasmBuiltinsAssembler) {            \
    TNode<WasmInstanceObject> instance = LoadInstanceFromFrame(); \
    TNode<Context> context = LoadContextFromInstance(instance);   \
    MessageTemplate message_id =                                  \
        wasm::WasmOpcodes::TrapReasonToMessageId(wasm::k##name);  \
    TailCallRuntime(Runtime::kThrowWasmError, context,            \
                    SmiConstant(static_cast<int>(message_id)));   \
  }
FOREACH_WASM_TRAPREASON(DECLARE_THROW_RUNTIME_FN)
#undef DECLARE_THROW_RUNTIME_FN

}  // namespace internal
}  // namespace v8