// Copyright 2014 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.

#ifndef V8_CCTEST_COMPILER_CODEGEN_TESTER_H_
#define V8_CCTEST_COMPILER_CODEGEN_TESTER_H_

#include "src/compilation-info.h"
#include "src/compiler/instruction-selector.h"
#include "src/compiler/pipeline.h"
#include "src/compiler/raw-machine-assembler.h"
#include "src/simulator.h"
#include "test/cctest/cctest.h"
#include "test/cctest/compiler/call-tester.h"

namespace v8 {
namespace internal {
namespace compiler {

template <typename ReturnType>
class RawMachineAssemblerTester : public HandleAndZoneScope,
                                  public CallHelper<ReturnType>,
                                  public RawMachineAssembler {
 public:
  RawMachineAssemblerTester(MachineType p0 = MachineType::None(),
                            MachineType p1 = MachineType::None(),
                            MachineType p2 = MachineType::None(),
                            MachineType p3 = MachineType::None(),
                            MachineType p4 = MachineType::None())
      : HandleAndZoneScope(),
        CallHelper<ReturnType>(
            main_isolate(),
            CSignature::New(main_zone(), MachineTypeForC<ReturnType>(), p0, p1,
                            p2, p3, p4)),
        RawMachineAssembler(
            main_isolate(), new (main_zone()) Graph(main_zone()),
            Linkage::GetSimplifiedCDescriptor(
                main_zone(),
                CSignature::New(main_zone(), MachineTypeForC<ReturnType>(), p0,
                                p1, p2, p3, p4),
                true),
            MachineType::PointerRepresentation(),
            InstructionSelector::SupportedMachineOperatorFlags(),
            InstructionSelector::AlignmentRequirements()) {}

  virtual ~RawMachineAssemblerTester() {}

  void CheckNumber(double expected, Object* number) {
    CHECK(this->isolate()->factory()->NewNumber(expected)->SameValue(number));
  }

  void CheckString(const char* expected, Object* string) {
    CHECK(
        this->isolate()->factory()->InternalizeUtf8String(expected)->SameValue(
            string));
  }

  void GenerateCode() { Generate(); }

  Handle<Code> GetCode() {
    Generate();
    return code_.ToHandleChecked();
  }

 protected:
  virtual byte* Generate() {
    if (code_.is_null()) {
      Schedule* schedule = this->Export();
      CallDescriptor* call_descriptor = this->call_descriptor();
      Graph* graph = this->graph();
      CompilationInfo info(ArrayVector("testing"), main_isolate(), main_zone(),
                           Code::ComputeFlags(Code::STUB));
      code_ = Pipeline::GenerateCodeForTesting(&info, call_descriptor, graph,
                                               schedule);
    }
    return this->code_.ToHandleChecked()->entry();
  }

 private:
  MaybeHandle<Code> code_;
};


template <typename ReturnType>
class BufferedRawMachineAssemblerTester
    : public RawMachineAssemblerTester<int32_t> {
 public:
  BufferedRawMachineAssemblerTester(MachineType p0 = MachineType::None(),
                                    MachineType p1 = MachineType::None(),
                                    MachineType p2 = MachineType::None(),
                                    MachineType p3 = MachineType::None())
      : BufferedRawMachineAssemblerTester(ComputeParameterCount(p0, p1, p2, p3),
                                          p0, p1, p2, p3) {}

  virtual byte* Generate() { return RawMachineAssemblerTester::Generate(); }

  // The BufferedRawMachineAssemblerTester does not pass parameters directly
  // to the constructed IR graph. Instead it passes a pointer to the parameter
  // to the IR graph, and adds Load nodes to the IR graph to load the
  // parameters from memory. Thereby it is possible to pass 64 bit parameters
  // to the IR graph.
  Node* Parameter(size_t index) {
    CHECK(index < 4);
    return parameter_nodes_[index];
  }

  // The BufferedRawMachineAssemblerTester adds a Store node to the IR graph
  // to store the graph's return value in memory. The memory address for the
  // Store node is provided as a parameter. By storing the return value in
  // memory it is possible to return 64 bit values.
  void Return(Node* input) {
    Store(MachineTypeForC<ReturnType>().representation(),
          RawMachineAssembler::Parameter(return_parameter_index_), input,
          kNoWriteBarrier);
    RawMachineAssembler::Return(Int32Constant(1234));
  }

  ReturnType Call() {
    ReturnType return_value;
    CSignature::VerifyParams(test_graph_signature_);
    CallHelper<int32_t>::Call(reinterpret_cast<void*>(&return_value));
    return return_value;
  }

  template <typename P0>
  ReturnType Call(P0 p0) {
    ReturnType return_value;
    CSignature::VerifyParams<P0>(test_graph_signature_);
    CallHelper<int32_t>::Call(reinterpret_cast<void*>(&p0),
                              reinterpret_cast<void*>(&return_value));
    return return_value;
  }

  template <typename P0, typename P1>
  ReturnType Call(P0 p0, P1 p1) {
    ReturnType return_value;
    CSignature::VerifyParams<P0, P1>(test_graph_signature_);
    CallHelper<int32_t>::Call(reinterpret_cast<void*>(&p0),
                              reinterpret_cast<void*>(&p1),
                              reinterpret_cast<void*>(&return_value));
    return return_value;
  }

  template <typename P0, typename P1, typename P2>
  ReturnType Call(P0 p0, P1 p1, P2 p2) {
    ReturnType return_value;
    CSignature::VerifyParams<P0, P1, P2>(test_graph_signature_);
    CallHelper<int32_t>::Call(
        reinterpret_cast<void*>(&p0), reinterpret_cast<void*>(&p1),
        reinterpret_cast<void*>(&p2), reinterpret_cast<void*>(&return_value));
    return return_value;
  }

  template <typename P0, typename P1, typename P2, typename P3>
  ReturnType Call(P0 p0, P1 p1, P2 p2, P3 p3) {
    ReturnType return_value;
    CSignature::VerifyParams<P0, P1, P2, P3>(test_graph_signature_);
    CallHelper<int32_t>::Call(
        reinterpret_cast<void*>(&p0), reinterpret_cast<void*>(&p1),
        reinterpret_cast<void*>(&p2), reinterpret_cast<void*>(&p3),
        reinterpret_cast<void*>(&return_value));
    return return_value;
  }

 private:
  BufferedRawMachineAssemblerTester(uint32_t return_parameter_index,
                                    MachineType p0, MachineType p1,
                                    MachineType p2, MachineType p3)
      : RawMachineAssemblerTester<int32_t>(
            MachineType::Pointer(),
            p0 == MachineType::None() ? MachineType::None()
                                      : MachineType::Pointer(),
            p1 == MachineType::None() ? MachineType::None()
                                      : MachineType::Pointer(),
            p2 == MachineType::None() ? MachineType::None()
                                      : MachineType::Pointer(),
            p3 == MachineType::None() ? MachineType::None()
                                      : MachineType::Pointer()),
        test_graph_signature_(
            CSignature::New(main_zone(), MachineType::Int32(), p0, p1, p2, p3)),
        return_parameter_index_(return_parameter_index) {
    parameter_nodes_[0] = p0 == MachineType::None()
                              ? nullptr
                              : Load(p0, RawMachineAssembler::Parameter(0));
    parameter_nodes_[1] = p1 == MachineType::None()
                              ? nullptr
                              : Load(p1, RawMachineAssembler::Parameter(1));
    parameter_nodes_[2] = p2 == MachineType::None()
                              ? nullptr
                              : Load(p2, RawMachineAssembler::Parameter(2));
    parameter_nodes_[3] = p3 == MachineType::None()
                              ? nullptr
                              : Load(p3, RawMachineAssembler::Parameter(3));
  }


  static uint32_t ComputeParameterCount(MachineType p0, MachineType p1,
                                        MachineType p2, MachineType p3) {
    if (p0 == MachineType::None()) {
      return 0;
    }
    if (p1 == MachineType::None()) {
      return 1;
    }
    if (p2 == MachineType::None()) {
      return 2;
    }
    if (p3 == MachineType::None()) {
      return 3;
    }
    return 4;
  }


  CSignature* test_graph_signature_;
  Node* parameter_nodes_[4];
  uint32_t return_parameter_index_;
};


template <>
class BufferedRawMachineAssemblerTester<void>
    : public RawMachineAssemblerTester<void> {
 public:
  BufferedRawMachineAssemblerTester(MachineType p0 = MachineType::None(),
                                    MachineType p1 = MachineType::None(),
                                    MachineType p2 = MachineType::None(),
                                    MachineType p3 = MachineType::None())
      : RawMachineAssemblerTester<void>(
            p0 == MachineType::None() ? MachineType::None()
                                      : MachineType::Pointer(),
            p1 == MachineType::None() ? MachineType::None()
                                      : MachineType::Pointer(),
            p2 == MachineType::None() ? MachineType::None()
                                      : MachineType::Pointer(),
            p3 == MachineType::None() ? MachineType::None()
                                      : MachineType::Pointer()),
        test_graph_signature_(
            CSignature::New(RawMachineAssemblerTester<void>::main_zone(),
                            MachineType::None(), p0, p1, p2, p3)) {
    parameter_nodes_[0] = p0 == MachineType::None()
                              ? nullptr
                              : Load(p0, RawMachineAssembler::Parameter(0));
    parameter_nodes_[1] = p1 == MachineType::None()
                              ? nullptr
                              : Load(p1, RawMachineAssembler::Parameter(1));
    parameter_nodes_[2] = p2 == MachineType::None()
                              ? nullptr
                              : Load(p2, RawMachineAssembler::Parameter(2));
    parameter_nodes_[3] = p3 == MachineType::None()
                              ? nullptr
                              : Load(p3, RawMachineAssembler::Parameter(3));
  }

  virtual byte* Generate() { return RawMachineAssemblerTester::Generate(); }

  // The BufferedRawMachineAssemblerTester does not pass parameters directly
  // to the constructed IR graph. Instead it passes a pointer to the parameter
  // to the IR graph, and adds Load nodes to the IR graph to load the
  // parameters from memory. Thereby it is possible to pass 64 bit parameters
  // to the IR graph.
  Node* Parameter(size_t index) {
    CHECK(index < 4);
    return parameter_nodes_[index];
  }


  void Call() {
    CSignature::VerifyParams(test_graph_signature_);
    CallHelper<void>::Call();
  }

  template <typename P0>
  void Call(P0 p0) {
    CSignature::VerifyParams<P0>(test_graph_signature_);
    CallHelper<void>::Call(reinterpret_cast<void*>(&p0));
  }

  template <typename P0, typename P1>
  void Call(P0 p0, P1 p1) {
    CSignature::VerifyParams<P0, P1>(test_graph_signature_);
    CallHelper<void>::Call(reinterpret_cast<void*>(&p0),
                           reinterpret_cast<void*>(&p1));
  }

  template <typename P0, typename P1, typename P2>
  void Call(P0 p0, P1 p1, P2 p2) {
    CSignature::VerifyParams<P0, P1, P2>(test_graph_signature_);
    CallHelper<void>::Call(reinterpret_cast<void*>(&p0),
                           reinterpret_cast<void*>(&p1),
                           reinterpret_cast<void*>(&p2));
  }

  template <typename P0, typename P1, typename P2, typename P3>
  void Call(P0 p0, P1 p1, P2 p2, P3 p3) {
    CSignature::VerifyParams<P0, P1, P2, P3>(test_graph_signature_);
    CallHelper<void>::Call(
        reinterpret_cast<void*>(&p0), reinterpret_cast<void*>(&p1),
        reinterpret_cast<void*>(&p2), reinterpret_cast<void*>(&p3));
  }

 private:
  CSignature* test_graph_signature_;
  Node* parameter_nodes_[4];
};
static const bool USE_RESULT_BUFFER = true;
static const bool USE_RETURN_REGISTER = false;
static const int32_t CHECK_VALUE = 0x99BEEDCE;


// TODO(titzer): use the C-style calling convention, or any register-based
// calling convention for binop tests.
template <typename CType, bool use_result_buffer>
class BinopTester {
 public:
  explicit BinopTester(RawMachineAssemblerTester<int32_t>* tester,
                       MachineType rep)
      : T(tester),
        param0(T->LoadFromPointer(&p0, rep)),
        param1(T->LoadFromPointer(&p1, rep)),
        rep(rep),
        p0(static_cast<CType>(0)),
        p1(static_cast<CType>(0)),
        result(static_cast<CType>(0)) {}

  RawMachineAssemblerTester<int32_t>* T;
  Node* param0;
  Node* param1;

  CType call(CType a0, CType a1) {
    p0 = a0;
    p1 = a1;
    if (use_result_buffer) {
      CHECK_EQ(CHECK_VALUE, T->Call());
      return result;
    } else {
      return static_cast<CType>(T->Call());
    }
  }

  void AddReturn(Node* val) {
    if (use_result_buffer) {
      T->Store(rep.representation(), T->PointerConstant(&result),
               T->Int32Constant(0), val, kNoWriteBarrier);
      T->Return(T->Int32Constant(CHECK_VALUE));
    } else {
      T->Return(val);
    }
  }

  template <typename Ci, typename Cj, typename Fn>
  void Run(const Ci& ci, const Cj& cj, const Fn& fn) {
    typename Ci::const_iterator i;
    typename Cj::const_iterator j;
    for (i = ci.begin(); i != ci.end(); ++i) {
      for (j = cj.begin(); j != cj.end(); ++j) {
        CHECK_EQ(fn(*i, *j), this->call(*i, *j));
      }
    }
  }

 protected:
  MachineType rep;
  CType p0;
  CType p1;
  CType result;
};


// A helper class for testing code sequences that take two int parameters and
// return an int value.
class Int32BinopTester : public BinopTester<int32_t, USE_RETURN_REGISTER> {
 public:
  explicit Int32BinopTester(RawMachineAssemblerTester<int32_t>* tester)
      : BinopTester<int32_t, USE_RETURN_REGISTER>(tester,
                                                  MachineType::Int32()) {}
};


// A helper class for testing code sequences that take two int parameters and
// return an int value.
class Int64BinopTester : public BinopTester<int64_t, USE_RETURN_REGISTER> {
 public:
  explicit Int64BinopTester(RawMachineAssemblerTester<int32_t>* tester)
      : BinopTester<int64_t, USE_RETURN_REGISTER>(tester,
                                                  MachineType::Int64()) {}
};


// A helper class for testing code sequences that take two uint parameters and
// return an uint value.
class Uint32BinopTester : public BinopTester<uint32_t, USE_RETURN_REGISTER> {
 public:
  explicit Uint32BinopTester(RawMachineAssemblerTester<int32_t>* tester)
      : BinopTester<uint32_t, USE_RETURN_REGISTER>(tester,
                                                   MachineType::Uint32()) {}

  uint32_t call(uint32_t a0, uint32_t a1) {
    p0 = a0;
    p1 = a1;
    return static_cast<uint32_t>(T->Call());
  }
};


// A helper class for testing code sequences that take two float parameters and
// return a float value.
class Float32BinopTester : public BinopTester<float, USE_RESULT_BUFFER> {
 public:
  explicit Float32BinopTester(RawMachineAssemblerTester<int32_t>* tester)
      : BinopTester<float, USE_RESULT_BUFFER>(tester, MachineType::Float32()) {}
};


// A helper class for testing code sequences that take two double parameters and
// return a double value.
class Float64BinopTester : public BinopTester<double, USE_RESULT_BUFFER> {
 public:
  explicit Float64BinopTester(RawMachineAssemblerTester<int32_t>* tester)
      : BinopTester<double, USE_RESULT_BUFFER>(tester, MachineType::Float64()) {
  }
};


// A helper class for testing code sequences that take two pointer parameters
// and return a pointer value.
// TODO(titzer): pick word size of pointers based on V8_TARGET.
template <typename Type>
class PointerBinopTester : public BinopTester<Type*, USE_RETURN_REGISTER> {
 public:
  explicit PointerBinopTester(RawMachineAssemblerTester<int32_t>* tester)
      : BinopTester<Type*, USE_RETURN_REGISTER>(tester,
                                                MachineType::Pointer()) {}
};


// A helper class for testing code sequences that take two tagged parameters and
// return a tagged value.
template <typename Type>
class TaggedBinopTester : public BinopTester<Type*, USE_RETURN_REGISTER> {
 public:
  explicit TaggedBinopTester(RawMachineAssemblerTester<int32_t>* tester)
      : BinopTester<Type*, USE_RETURN_REGISTER>(tester,
                                                MachineType::AnyTagged()) {}
};

// A helper class for testing compares. Wraps a machine opcode and provides
// evaluation routines and the operators.
class CompareWrapper {
 public:
  explicit CompareWrapper(IrOpcode::Value op) : opcode(op) {}

  Node* MakeNode(RawMachineAssemblerTester<int32_t>* m, Node* a, Node* b) {
    return m->AddNode(op(m->machine()), a, b);
  }

  const Operator* op(MachineOperatorBuilder* machine) {
    switch (opcode) {
      case IrOpcode::kWord32Equal:
        return machine->Word32Equal();
      case IrOpcode::kInt32LessThan:
        return machine->Int32LessThan();
      case IrOpcode::kInt32LessThanOrEqual:
        return machine->Int32LessThanOrEqual();
      case IrOpcode::kUint32LessThan:
        return machine->Uint32LessThan();
      case IrOpcode::kUint32LessThanOrEqual:
        return machine->Uint32LessThanOrEqual();
      case IrOpcode::kFloat64Equal:
        return machine->Float64Equal();
      case IrOpcode::kFloat64LessThan:
        return machine->Float64LessThan();
      case IrOpcode::kFloat64LessThanOrEqual:
        return machine->Float64LessThanOrEqual();
      default:
        UNREACHABLE();
    }
    return NULL;
  }

  bool Int32Compare(int32_t a, int32_t b) {
    switch (opcode) {
      case IrOpcode::kWord32Equal:
        return a == b;
      case IrOpcode::kInt32LessThan:
        return a < b;
      case IrOpcode::kInt32LessThanOrEqual:
        return a <= b;
      case IrOpcode::kUint32LessThan:
        return static_cast<uint32_t>(a) < static_cast<uint32_t>(b);
      case IrOpcode::kUint32LessThanOrEqual:
        return static_cast<uint32_t>(a) <= static_cast<uint32_t>(b);
      default:
        UNREACHABLE();
    }
    return false;
  }

  bool Float64Compare(double a, double b) {
    switch (opcode) {
      case IrOpcode::kFloat64Equal:
        return a == b;
      case IrOpcode::kFloat64LessThan:
        return a < b;
      case IrOpcode::kFloat64LessThanOrEqual:
        return a <= b;
      default:
        UNREACHABLE();
    }
    return false;
  }

  IrOpcode::Value opcode;
};


// A small closure class to generate code for a function of two inputs that
// produces a single output so that it can be used in many different contexts.
// The {expected()} method should compute the expected output for a given
// pair of inputs.
template <typename T>
class BinopGen {
 public:
  virtual void gen(RawMachineAssemblerTester<int32_t>* m, Node* a, Node* b) = 0;
  virtual T expected(T a, T b) = 0;
  virtual ~BinopGen() {}
};

// A helper class to generate various combination of input shape combinations
// and run the generated code to ensure it produces the correct results.
class Int32BinopInputShapeTester {
 public:
  explicit Int32BinopInputShapeTester(BinopGen<int32_t>* g)
      : gen(g), input_a(0), input_b(0) {}

  void TestAllInputShapes();

 private:
  BinopGen<int32_t>* gen;
  int32_t input_a;
  int32_t input_b;

  void Run(RawMachineAssemblerTester<int32_t>* m);
  void RunLeft(RawMachineAssemblerTester<int32_t>* m);
  void RunRight(RawMachineAssemblerTester<int32_t>* m);
};
}  // namespace compiler
}  // namespace internal
}  // namespace v8

#endif  // V8_CCTEST_COMPILER_CODEGEN_TESTER_H_
