// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

#include "vm/compiler/backend/locations_helpers.h"
#include "vm/dart_api_impl.h"
#include "vm/dart_entry.h"
#include "vm/unit_test.h"

namespace dart {

#define Reg(index) (static_cast<Register>(index))
#define Fpu(index) (static_cast<FpuRegister>(index))

#define ReqReg Location::RequiresRegister()
#define ReqFpu Location::RequiresFpuRegister()
#define RegLoc(index) Location::RegisterLocation(Reg(index))
#define FpuLoc(index) Location::FpuRegisterLocation(Fpu(index))

typedef ZoneGrowableArray<Location> LocationArray;

static LocationArray* MakeLocationArray() {
  LocationArray* arr = new LocationArray();
  return arr;
}

static LocationArray* MakeLocationArray(Location loc0) {
  LocationArray* arr = new LocationArray();
  arr->Add(loc0);
  return arr;
}

static LocationArray* MakeLocationArray(Location loc0, Location loc1) {
  LocationArray* arr = new LocationArray();
  arr->Add(loc0);
  arr->Add(loc1);
  return arr;
}

static void ValidateSummary(LocationSummary* locs,
                            Location expected_output,
                            const LocationArray* expected_inputs,
                            const LocationArray* expected_temps) {
  EXPECT(locs->out(0).Equals(expected_output));
  EXPECT_EQ(expected_inputs->length(), locs->input_count());
  for (intptr_t i = 0; i < expected_inputs->length(); i++) {
    EXPECT(locs->in(i).Equals(expected_inputs->At(i)));
  }
  EXPECT_EQ(expected_temps->length(), locs->temp_count());
  for (intptr_t i = 0; i < expected_temps->length(); i++) {
    EXPECT(locs->temp(i).Equals(expected_temps->At(i)));
  }
}

static void FillSummary(LocationSummary* locs,
                        Location expected_output,
                        const LocationArray* expected_inputs,
                        const LocationArray* expected_temps) {
  locs->set_out(0, expected_output);
  for (intptr_t i = 0; i < expected_inputs->length(); i++) {
    locs->set_in(i, expected_inputs->At(i));
  }
  for (intptr_t i = 0; i < expected_temps->length(); i++) {
    locs->set_temp(i, expected_temps->At(i));
  }
}

class MockInstruction : public ZoneObject {
 public:
  virtual ~MockInstruction() {}

  LocationSummary* locs() {
    if (locs_ == nullptr) {
      locs_ = MakeLocationSummary(Thread::Current()->zone(), false);
    }
    return locs_;
  }

  virtual LocationSummary* MakeLocationSummary(Zone* zone, bool opt) const = 0;
  virtual void EmitNativeCode(FlowGraphCompiler* compiler) = 0;

 private:
  LocationSummary* locs_;
};

#define INSTRUCTION_TEST(Name, Arity, Signature, ExpectedOut, ExpectedIn,      \
                         ExpectedTemp, AllocatedOut, AllocatedIn,              \
                         AllocatedTemp)                                        \
  class Name##Instr : public MockInstruction {                                 \
   public:                                                                     \
    LocationSummary* MakeLocationSummary(Zone* zone, bool opt) const;          \
    void EmitNativeCode(FlowGraphCompiler* compiler);                          \
    virtual intptr_t InputCount() const { return Arity; }                      \
  };                                                                           \
  TEST_CASE(LocationsHelpers_##Name) {                                         \
    const Location expected_out = ExpectedOut;                                 \
    const LocationArray* expected_inputs = MakeLocationArray ExpectedIn;       \
    const LocationArray* expected_temps = MakeLocationArray ExpectedTemp;      \
                                                                               \
    const Location allocated_out = AllocatedOut;                               \
    const LocationArray* allocated_inputs = MakeLocationArray AllocatedIn;     \
    const LocationArray* allocated_temps = MakeLocationArray AllocatedTemp;    \
                                                                               \
    Name##Instr* instr = new Name##Instr();                                    \
    LocationSummary* locs = instr->locs();                                     \
                                                                               \
    ValidateSummary(locs, expected_out, expected_inputs, expected_temps);      \
    FillSummary(locs, allocated_out, allocated_inputs, allocated_temps);       \
                                                                               \
    instr->EmitNativeCode(nullptr);                                            \
  }                                                                            \
  DEFINE_BACKEND(Name, Signature)

// Reg -> Reg
INSTRUCTION_TEST(Unary,
                 1,
                 (Register out, Register in),
                 ReqReg,
                 (ReqReg),
                 (),
                 RegLoc(0),
                 (RegLoc(1)),
                 ()) {
  EXPECT_EQ(Reg(0), out);
  EXPECT_EQ(Reg(1), in);
}

// (Reg, Fpu) -> Reg
INSTRUCTION_TEST(Binary1,
                 2,
                 (Register out, Register in0, FpuRegister in1),
                 ReqReg,
                 (ReqReg, Location::RequiresFpuRegister()),
                 (),
                 RegLoc(0),
                 (RegLoc(1), FpuLoc(2)),
                 ()) {
  EXPECT_EQ(Reg(0), out);
  EXPECT_EQ(Reg(1), in0);
  EXPECT_EQ(Fpu(2), in1);
}

// (Fpu, Reg) -> Reg
INSTRUCTION_TEST(Binary2,
                 2,
                 (Register out, FpuRegister in0, Register in1),
                 ReqReg,
                 (ReqFpu, ReqReg),
                 (),
                 RegLoc(0),
                 (FpuLoc(1), RegLoc(2)),
                 ()) {
  EXPECT_EQ(Reg(0), out);
  EXPECT_EQ(Fpu(1), in0);
  EXPECT_EQ(Reg(2), in1);
}

// -> Reg(3)
INSTRUCTION_TEST(FixedOutput,
                 0,
                 (Fixed<Register, Reg(3)> out),
                 RegLoc(3),
                 (),
                 (),
                 RegLoc(3),
                 (),
                 ()) {
  EXPECT_EQ(Reg(3), Reg(out));
}

// Fpu(3) -> Fpu
INSTRUCTION_TEST(FixedInput,
                 1,
                 (FpuRegister out, Fixed<FpuRegister, Fpu(3)> in),
                 ReqFpu,
                 (FpuLoc(3)),
                 (),
                 FpuLoc(0),
                 (FpuLoc(3)),
                 ()) {
  EXPECT_EQ(Fpu(0), out);
  EXPECT_EQ(Fpu(3), Fpu(in));
}

// Reg -> SameAsFirstInput
INSTRUCTION_TEST(SameAsFirstInput,
                 2,
                 (SameAsFirstInput, Register in0, Register in1),
                 Location::SameAsFirstInput(),
                 (ReqReg, ReqReg),
                 (),
                 RegLoc(0),
                 (RegLoc(0), RegLoc(1)),
                 ()) {
  EXPECT_EQ(Reg(0), in0);
  EXPECT_EQ(Reg(1), in1);
}

// {Temps: Fpu, Reg} (Reg, Fpu) -> Reg
INSTRUCTION_TEST(Temps,
                 2,
                 (Register out,
                  Register in0,
                  FpuRegister in1,
                  Temp<FpuRegister> temp0,
                  Temp<Register> temp1),
                 ReqReg,
                 (ReqReg, ReqFpu),
                 (ReqFpu, ReqReg),
                 RegLoc(0),
                 (RegLoc(1), FpuLoc(2)),
                 (FpuLoc(3), RegLoc(4))) {
  EXPECT_EQ(Reg(0), out);
  EXPECT_EQ(Reg(1), in0);
  EXPECT_EQ(Fpu(2), in1);
  EXPECT_EQ(Fpu(3), Fpu(temp0));
  EXPECT_EQ(Reg(4), Reg(temp1));
}

// {Temps: Fpu(3)} -> Fpu
INSTRUCTION_TEST(FixedTemp,
                 0,
                 (FpuRegister out, Temp<Fixed<FpuRegister, Fpu(3)> > temp),
                 ReqFpu,
                 (),
                 (FpuLoc(3)),
                 FpuLoc(4),
                 (),
                 (FpuLoc(3))) {
  EXPECT_EQ(Fpu(4), out);
  EXPECT_EQ(Fpu(3), Fpu(temp));
}

}  // namespace dart
