// Copyright 2019, VIXL authors
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
//   * Redistributions of source code must retain the above copyright notice,
//     this list of conditions and the following disclaimer.
//   * Redistributions in binary form must reproduce the above copyright notice,
//     this list of conditions and the following disclaimer in the documentation
//     and/or other materials provided with the distribution.
//   * Neither the name of ARM Limited nor the names of its contributors may be
//     used to endorse or promote products derived from this software without
//     specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Test infrastructure.
//
// Tests are functions which accept no parameters and have no return values.
// The testing code should not perform an explicit return once completed. For
// example to test the mov immediate instruction a very simple test would be:
//
//   TEST(mov_x0_one) {
//     SETUP();
//
//     START();
//     __ mov(x0, 1);
//     END();
//
//     if (CAN_RUN()) {
//       RUN();
//
//       ASSERT_EQUAL_64(1, x0);
//     }
//   }
//
// Within a START ... END block all registers but sp can be modified. sp has to
// be explicitly saved/restored. The END() macro replaces the function return
// so it may appear multiple times in a test if the test has multiple exit
// points.
//
// Tests requiring specific CPU features should specify exactly what they
// require using SETUP_WITH_FEATURES(...) instead of SETUP().
//
// Once the test has been run all integer and floating point registers as well
// as flags are accessible through a RegisterDump instance, see
// utils-aarch64.cc for more info on RegisterDump.
//
// We provide some helper assert to handle common cases:
//
//   ASSERT_EQUAL_32(int32_t, int_32t)
//   ASSERT_EQUAL_FP32(float, float)
//   ASSERT_EQUAL_32(int32_t, W register)
//   ASSERT_EQUAL_FP32(float, S register)
//   ASSERT_EQUAL_64(int64_t, int_64t)
//   ASSERT_EQUAL_FP64(double, double)
//   ASSERT_EQUAL_64(int64_t, X register)
//   ASSERT_EQUAL_64(X register, X register)
//   ASSERT_EQUAL_FP64(double, D register)
//
// e.g. ASSERT_EQUAL_64(0.5, d30);
//
// If more advanced computation is required before the assert then access the
// RegisterDump named core directly:
//
//   ASSERT_EQUAL_64(0x1234, core->reg_x0() & 0xffff);

namespace vixl {
namespace aarch64 {

#define __ masm.
#define TEST(name) TEST_(AARCH64_ASM_##name)

#ifdef VIXL_INCLUDE_SIMULATOR_AARCH64
// Run tests with the simulator.

#define SETUP()        \
  MacroAssembler masm; \
  SETUP_COMMON();      \
  SETUP_COMMON_SIM()

#define SETUP_WITH_FEATURES(...)                 \
  MacroAssembler masm;                           \
  SETUP_COMMON();                                \
  SETUP_COMMON_SIM();                            \
  masm.SetCPUFeatures(CPUFeatures(__VA_ARGS__)); \
  simulator.SetCPUFeatures(CPUFeatures(__VA_ARGS__))

#define SETUP_CUSTOM(size, pic)                                  \
  MacroAssembler masm(size + CodeBuffer::kDefaultCapacity, pic); \
  SETUP_COMMON();                                                \
  SETUP_COMMON_SIM()

#define SETUP_CUSTOM_SIM(...)                                   \
  MacroAssembler masm;                                          \
  SETUP_COMMON();                                               \
  Simulator simulator(&simulator_decoder, stdout, __VA_ARGS__); \
  simulator.SetColouredTrace(Test::coloured_trace());           \
  simulator.SetCPUFeatures(CPUFeatures::None())

#define SETUP_COMMON()                                                   \
  bool queried_can_run = false;                                          \
  bool printed_sve_lane_warning = false;                                 \
  /* Avoid unused-variable warnings in case a test never calls RUN(). */ \
  USE(queried_can_run);                                                  \
  USE(printed_sve_lane_warning);                                         \
  masm.SetCPUFeatures(CPUFeatures::None());                              \
  masm.SetGenerateSimulatorCode(true);                                   \
  Decoder simulator_decoder;                                             \
  RegisterDump core;                                                     \
  ptrdiff_t offset_after_infrastructure_start;                           \
  ptrdiff_t offset_before_infrastructure_end

#define SETUP_COMMON_SIM()                            \
  Simulator simulator(&simulator_decoder);            \
  simulator.SetColouredTrace(Test::coloured_trace()); \
  simulator.SetCPUFeatures(CPUFeatures::None())

#define START()                                                               \
  masm.Reset();                                                               \
  simulator.ResetState();                                                     \
  {                                                                           \
    SimulationCPUFeaturesScope cpu(&masm, kInfrastructureCPUFeatures);        \
    __ PushCalleeSavedRegisters();                                            \
  }                                                                           \
  {                                                                           \
    int trace_parameters = 0;                                                 \
    if (Test::trace_reg()) trace_parameters |= LOG_STATE;                     \
    if (Test::trace_write()) trace_parameters |= LOG_WRITE;                   \
    if (Test::trace_sim()) trace_parameters |= LOG_DISASM;                    \
    if (Test::trace_branch()) trace_parameters |= LOG_BRANCH;                 \
    if (trace_parameters != 0) {                                              \
      __ Trace(static_cast<TraceParameters>(trace_parameters), TRACE_ENABLE); \
    }                                                                         \
  }                                                                           \
  offset_after_infrastructure_start = masm.GetCursorOffset();                 \
  /* Avoid unused-variable warnings in case a test never calls RUN(). */      \
  USE(offset_after_infrastructure_start)

#define END()                                                            \
  offset_before_infrastructure_end = masm.GetCursorOffset();             \
  /* Avoid unused-variable warnings in case a test never calls RUN(). */ \
  USE(offset_before_infrastructure_end);                                 \
  __ Trace(LOG_ALL, TRACE_DISABLE);                                      \
  {                                                                      \
    SimulationCPUFeaturesScope cpu(&masm, kInfrastructureCPUFeatures);   \
    core.Dump(&masm);                                                    \
    __ PopCalleeSavedRegisters();                                        \
  }                                                                      \
  __ Ret();                                                              \
  masm.FinalizeCode()

#define RUN()                                                                  \
  RUN_WITHOUT_SEEN_FEATURE_CHECK();                                            \
  {                                                                            \
    /* We expect the test to use all of the features it requested, plus the */ \
    /* features that the instructure code requires.                         */ \
    CPUFeatures const& expected_features =                                     \
        simulator.GetCPUFeatures()->With(CPUFeatures::kNEON);                  \
    CPUFeatures const& seen = simulator.GetSeenFeatures();                     \
    /* This gives three broad categories of features that we care about:    */ \
    /*  1. Things both expected and seen.                                   */ \
    /*  2. Things seen, but not expected. The simulator catches these.      */ \
    /*  3. Things expected, but not seen. We check these here.              */ \
    /* In a valid, passing test, categories 2 and 3 should be empty.        */ \
    if (seen != expected_features) {                                           \
      /* The Simulator should have caught anything in category 2 already.   */ \
      VIXL_ASSERT(expected_features.Has(seen));                                \
      /* Anything left is category 3: things expected, but not seen. This   */ \
      /* is not necessarily a bug in VIXL itself, but indicates that the    */ \
      /* test is less strict than it could be.                              */ \
      CPUFeatures missing = expected_features.Without(seen);                   \
      VIXL_ASSERT(missing.Count() > 0);                                        \
      std::cout << "Error: expected to see CPUFeatures { " << missing          \
                << " }\n";                                                     \
      VIXL_ABORT();                                                            \
    }                                                                          \
  }

#define RUN_WITHOUT_SEEN_FEATURE_CHECK() \
  DISASSEMBLE();                         \
  VIXL_ASSERT(QUERIED_CAN_RUN());        \
  VIXL_ASSERT(CAN_RUN());                \
  simulator.RunFrom(masm.GetBuffer()->GetStartAddress<Instruction*>())

#else  // ifdef VIXL_INCLUDE_SIMULATOR_AARCH64.
#define SETUP()        \
  MacroAssembler masm; \
  SETUP_COMMON()

#define SETUP_WITH_FEATURES(...) \
  MacroAssembler masm;           \
  SETUP_COMMON();                \
  masm.SetCPUFeatures(CPUFeatures(__VA_ARGS__))

#define SETUP_CUSTOM(size, pic)                             \
  size_t buffer_size = size + CodeBuffer::kDefaultCapacity; \
  MacroAssembler masm(buffer_size, pic);                    \
  SETUP_COMMON()

#define SETUP_COMMON()                                                   \
  bool queried_can_run = false;                                          \
  bool printed_sve_lane_warning = false;                                 \
  /* Avoid unused-variable warnings in case a test never calls RUN(). */ \
  USE(queried_can_run);                                                  \
  USE(printed_sve_lane_warning);                                         \
  masm.SetCPUFeatures(CPUFeatures::None());                              \
  masm.SetGenerateSimulatorCode(false);                                  \
  RegisterDump core;                                                     \
  CPU::SetUp();                                                          \
  ptrdiff_t offset_after_infrastructure_start;                           \
  ptrdiff_t offset_before_infrastructure_end

#define START()                                                          \
  masm.Reset();                                                          \
  {                                                                      \
    CPUFeaturesScope cpu(&masm, kInfrastructureCPUFeatures);             \
    __ PushCalleeSavedRegisters();                                       \
  }                                                                      \
  offset_after_infrastructure_start = masm.GetCursorOffset();            \
  /* Avoid unused-variable warnings in case a test never calls RUN(). */ \
  USE(offset_after_infrastructure_start)

#define END()                                                            \
  offset_before_infrastructure_end = masm.GetCursorOffset();             \
  /* Avoid unused-variable warnings in case a test never calls RUN(). */ \
  USE(offset_before_infrastructure_end);                                 \
  {                                                                      \
    CPUFeaturesScope cpu(&masm, kInfrastructureCPUFeatures);             \
    core.Dump(&masm);                                                    \
    __ PopCalleeSavedRegisters();                                        \
  }                                                                      \
  __ Ret();                                                              \
  masm.FinalizeCode()

// Execute the generated code from the memory area.
#define RUN()                                               \
  DISASSEMBLE();                                            \
  VIXL_ASSERT(QUERIED_CAN_RUN());                           \
  VIXL_ASSERT(CAN_RUN());                                   \
  masm.GetBuffer()->SetExecutable();                        \
  ExecuteMemory(masm.GetBuffer()->GetStartAddress<byte*>(), \
                masm.GetSizeOfCodeGenerated());             \
  masm.GetBuffer()->SetWritable()

// This just provides compatibility with VIXL_INCLUDE_SIMULATOR_AARCH64 builds.
// We cannot run seen-feature checks when running natively.
#define RUN_WITHOUT_SEEN_FEATURE_CHECK() RUN()

#endif  // ifdef VIXL_INCLUDE_SIMULATOR_AARCH64.

#define CAN_RUN() CanRun(*masm.GetCPUFeatures(), &queried_can_run)
#define QUERIED_CAN_RUN() (queried_can_run)

#define DISASSEMBLE()                                                     \
  if (Test::disassemble()) {                                              \
    PrintDisassembler disasm(stdout);                                     \
    CodeBuffer* buffer = masm.GetBuffer();                                \
    Instruction* test_start = buffer->GetOffsetAddress<Instruction*>(     \
        offset_after_infrastructure_start);                               \
    Instruction* test_end = buffer->GetOffsetAddress<Instruction*>(       \
        offset_before_infrastructure_end);                                \
                                                                          \
    if (Test::disassemble_infrastructure()) {                             \
      Instruction* infra_start = buffer->GetStartAddress<Instruction*>(); \
      printf("# Infrastructure code (prologue)\n");                       \
      disasm.DisassembleBuffer(infra_start, test_start);                  \
      printf("# Test code\n");                                            \
    } else {                                                              \
      printf(                                                             \
          "# Warning: Omitting infrastructure code. "                     \
          "Use --disassemble to see it.\n");                              \
    }                                                                     \
                                                                          \
    disasm.DisassembleBuffer(test_start, test_end);                       \
                                                                          \
    if (Test::disassemble_infrastructure()) {                             \
      printf("# Infrastructure code (epilogue)\n");                       \
      Instruction* infra_end = buffer->GetEndAddress<Instruction*>();     \
      disasm.DisassembleBuffer(test_end, infra_end);                      \
    }                                                                     \
  }

#define ASSERT_EQUAL_NZCV(expected) \
  VIXL_CHECK(EqualNzcv(expected, core.flags_nzcv()))

#define ASSERT_EQUAL_REGISTERS(expected) \
  VIXL_CHECK(EqualRegisters(&expected, &core))

#define ASSERT_EQUAL_FP16(expected, result) \
  VIXL_CHECK(EqualFP16(expected, &core, result))

#define ASSERT_EQUAL_32(expected, result) \
  VIXL_CHECK(Equal32(static_cast<uint32_t>(expected), &core, result))

#define ASSERT_EQUAL_FP32(expected, result) \
  VIXL_CHECK(EqualFP32(expected, &core, result))

#define ASSERT_EQUAL_64(expected, result) \
  VIXL_CHECK(Equal64(expected, &core, result))

#define ASSERT_NOT_EQUAL_64(expected, result) \
  VIXL_CHECK(NotEqual64(expected, &core, result))

#define ASSERT_EQUAL_FP64(expected, result) \
  VIXL_CHECK(EqualFP64(expected, &core, result))

#define ASSERT_EQUAL_128(expected_h, expected_l, result) \
  VIXL_CHECK(Equal128(expected_h, expected_l, &core, result))

#define ASSERT_LITERAL_POOL_SIZE(expected) \
  VIXL_CHECK((expected + kInstructionSize) == (masm.GetLiteralPoolSize()))

#define ASSERT_EQUAL_SVE_LANE(expected, result, lane) \
  VIXL_CHECK(EqualSVELane(expected, &core, result, lane));

// If `expected` is scalar, check that every lane of `result` matches it.
// If `expected` is an array of N expected values, check that the first N
// lanes on `result` match. The rightmost (highest-indexed) array element maps
// to the lowest-numbered lane.
#define ASSERT_EQUAL_SVE(expected, result) \
  VIXL_CHECK(EqualSVE(expected, &core, result, &printed_sve_lane_warning))

#define ASSERT_EQUAL_MEMORY(expected, result, ...)          \
  VIXL_CHECK(EqualMemory(reinterpret_cast<void*>(expected), \
                         reinterpret_cast<void*>(result),   \
                         __VA_ARGS__))

#define MUST_FAIL_WITH_MESSAGE(code, message)                     \
  {                                                               \
    bool aborted = false;                                         \
    try {                                                         \
      code;                                                       \
    } catch (const std::runtime_error& e) {                       \
      const char* expected_error = message;                       \
      size_t error_length = strlen(expected_error);               \
      if (strncmp(expected_error, e.what(), error_length) == 0) { \
        aborted = true;                                           \
      } else {                                                    \
        printf("Mismatch in error message.\n");                   \
        printf("Expected: %s\n", expected_error);                 \
        printf("Found:    %s\n", e.what());                       \
      }                                                           \
    }                                                             \
    VIXL_CHECK(aborted);                                          \
  }

}  // namespace aarch64
}  // namespace vixl
