/*
 * Copyright (C) 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <dirent.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <fstream>
#include <map>

#include "gtest/gtest.h"

#include "jni/quick/calling_convention.h"
#include "utils/arm/jni_macro_assembler_arm_vixl.h"

#include "base/hex_dump.h"
#include "base/malloc_arena_pool.h"
#include "common_runtime_test.h"

namespace art {
namespace arm {

// Include results file (generated manually)
#include "assembler_thumb_test_expected.cc.inc"

#ifndef ART_TARGET_ANDROID
// This controls whether the results are printed to the
// screen or compared against the expected output.
// To generate new expected output, set this to true and
// copy the output into the .cc.inc file in the form
// of the other results.
//
// When this is false, the results are not printed to the
// output, but are compared against the expected results
// in the .cc.inc file.
static constexpr bool kPrintResults = false;
#endif

void SetAndroidData() {
  const char* data = getenv("ANDROID_DATA");
  if (data == nullptr) {
    setenv("ANDROID_DATA", "/tmp", 1);
  }
}

int CompareIgnoringSpace(const char* s1, const char* s2) {
  while (*s1 != '\0') {
    while (isspace(*s1)) ++s1;
    while (isspace(*s2)) ++s2;
    if (*s1 == '\0' || *s1 != *s2) {
      break;
    }
    ++s1;
    ++s2;
  }
  return *s1 - *s2;
}

void InitResults() {
  if (test_results.empty()) {
    setup_results();
  }
}

std::string GetToolsDir() {
#ifndef ART_TARGET_ANDROID
  // This will only work on the host.  There is no as, objcopy or objdump on the device.
  static std::string toolsdir;

  if (toolsdir.empty()) {
    setup_results();
    toolsdir = CommonRuntimeTest::GetAndroidTargetToolsDir(InstructionSet::kThumb2);
    SetAndroidData();
  }

  return toolsdir;
#else
  return std::string();
#endif
}

void DumpAndCheck(std::vector<uint8_t>& code, const char* testname, const char* const* results) {
#ifndef ART_TARGET_ANDROID
  static std::string toolsdir = GetToolsDir();

  ScratchFile file;

  const char* filename = file.GetFilename().c_str();

  std::ofstream out(filename);
  if (out) {
    out << ".section \".text\"\n";
    out << ".syntax unified\n";
    out << ".arch armv7-a\n";
    out << ".thumb\n";
    out << ".thumb_func\n";
    out << ".type " << testname << ", #function\n";
    out << ".global " << testname << "\n";
    out << testname << ":\n";
    out << ".fnstart\n";

    for (uint32_t i = 0 ; i < code.size(); ++i) {
      out << ".byte " << (static_cast<int>(code[i]) & 0xff) << "\n";
    }
    out << ".fnend\n";
    out << ".size " << testname << ", .-" << testname << "\n";
  }
  out.close();

  char cmd[1024];

  // Assemble the .S
  snprintf(cmd, sizeof(cmd), "%sas %s -o %s.o", toolsdir.c_str(), filename, filename);
  int cmd_result = system(cmd);
  ASSERT_EQ(cmd_result, 0) << cmd << strerror(errno);

  // Disassemble.
  snprintf(cmd, sizeof(cmd), "%sobjdump -D -M force-thumb --section=.text %s.o  | grep '^  *[0-9a-f][0-9a-f]*:'",
    toolsdir.c_str(), filename);
  if (kPrintResults) {
    // Print the results only, don't check. This is used to generate new output for inserting
    // into the .inc file, so let's add the appropriate prefix/suffix needed in the C++ code.
    strcat(cmd, " | sed '-es/^/  \"/' | sed '-es/$/\\\\n\",/'");
    int cmd_result3 = system(cmd);
    ASSERT_EQ(cmd_result3, 0) << strerror(errno);
  } else {
    // Check the results match the appropriate results in the .inc file.
    FILE *fp = popen(cmd, "r");
    ASSERT_TRUE(fp != nullptr);

    uint32_t lineindex = 0;

    while (!feof(fp)) {
      char testline[256];
      char *s = fgets(testline, sizeof(testline), fp);
      if (s == nullptr) {
        break;
      }
      if (CompareIgnoringSpace(results[lineindex], testline) != 0) {
        LOG(FATAL) << "Output is not as expected at line: " << lineindex
          << results[lineindex] << "/" << testline << ", test name: " << testname;
      }
      ++lineindex;
    }
    // Check that we are at the end.
    ASSERT_TRUE(results[lineindex] == nullptr);
    fclose(fp);
  }

  char buf[FILENAME_MAX];
  snprintf(buf, sizeof(buf), "%s.o", filename);
  unlink(buf);
#endif  // ART_TARGET_ANDROID
}

class ArmVIXLAssemblerTest : public ::testing::Test {
 public:
  ArmVIXLAssemblerTest() : pool(), allocator(&pool), assembler(&allocator) { }

  MallocArenaPool pool;
  ArenaAllocator allocator;
  ArmVIXLJNIMacroAssembler assembler;
};

#define __ assembler->

void EmitAndCheck(ArmVIXLJNIMacroAssembler* assembler, const char* testname,
                  const char* const* results) {
  __ FinalizeCode();
  size_t cs = __ CodeSize();
  std::vector<uint8_t> managed_code(cs);
  MemoryRegion code(&managed_code[0], managed_code.size());
  __ FinalizeInstructions(code);

  DumpAndCheck(managed_code, testname, results);
}

void EmitAndCheck(ArmVIXLJNIMacroAssembler* assembler, const char* testname) {
  InitResults();
  std::map<std::string, const char* const*>::iterator results = test_results.find(testname);
  ASSERT_NE(results, test_results.end());

  EmitAndCheck(assembler, testname, results->second);
}

#undef __

#define __ assembler.

TEST_F(ArmVIXLAssemblerTest, VixlJniHelpers) {
  // Run the test only with Baker read barriers, as the expected
  // generated code contains a Marking Register refresh instruction.
  TEST_DISABLED_WITHOUT_BAKER_READ_BARRIERS();

  const bool is_static = true;
  const bool is_synchronized = false;
  const bool is_critical_native = false;
  const char* shorty = "IIFII";

  std::unique_ptr<JniCallingConvention> jni_conv(
      JniCallingConvention::Create(&allocator,
                                   is_static,
                                   is_synchronized,
                                   is_critical_native,
                                   shorty,
                                   InstructionSet::kThumb2));
  std::unique_ptr<ManagedRuntimeCallingConvention> mr_conv(
      ManagedRuntimeCallingConvention::Create(
          &allocator, is_static, is_synchronized, shorty, InstructionSet::kThumb2));
  const int frame_size(jni_conv->FrameSize());
  ArrayRef<const ManagedRegister> callee_save_regs = jni_conv->CalleeSaveRegisters();

  const ManagedRegister method_register = ArmManagedRegister::FromCoreRegister(R0);
  const ManagedRegister scratch_register = ArmManagedRegister::FromCoreRegister(R12);

  __ BuildFrame(frame_size, mr_conv->MethodRegister(), callee_save_regs, mr_conv->EntrySpills());
  __ IncreaseFrameSize(32);

  // Loads
  __ IncreaseFrameSize(4096);
  __ Load(method_register, FrameOffset(32), 4);
  __ Load(method_register, FrameOffset(124), 4);
  __ Load(method_register, FrameOffset(132), 4);
  __ Load(method_register, FrameOffset(1020), 4);
  __ Load(method_register, FrameOffset(1024), 4);
  __ Load(scratch_register, FrameOffset(4092), 4);
  __ Load(scratch_register, FrameOffset(4096), 4);
  __ LoadRawPtrFromThread(scratch_register, ThreadOffset32(512));
  __ LoadRef(method_register, scratch_register, MemberOffset(128), /* unpoison_reference= */ false);

  // Stores
  __ Store(FrameOffset(32), method_register, 4);
  __ Store(FrameOffset(124), method_register, 4);
  __ Store(FrameOffset(132), method_register, 4);
  __ Store(FrameOffset(1020), method_register, 4);
  __ Store(FrameOffset(1024), method_register, 4);
  __ Store(FrameOffset(4092), scratch_register, 4);
  __ Store(FrameOffset(4096), scratch_register, 4);
  __ StoreImmediateToFrame(FrameOffset(48), 0xFF, scratch_register);
  __ StoreImmediateToFrame(FrameOffset(48), 0xFFFFFF, scratch_register);
  __ StoreRawPtr(FrameOffset(48), scratch_register);
  __ StoreRef(FrameOffset(48), scratch_register);
  __ StoreSpanning(FrameOffset(48), method_register, FrameOffset(48), scratch_register);
  __ StoreStackOffsetToThread(ThreadOffset32(512), FrameOffset(4096), scratch_register);
  __ StoreStackPointerToThread(ThreadOffset32(512));

  // Other
  __ Call(method_register, FrameOffset(48), scratch_register);
  __ Copy(FrameOffset(48), FrameOffset(44), scratch_register, 4);
  __ CopyRawPtrFromThread(FrameOffset(44), ThreadOffset32(512), scratch_register);
  __ CopyRef(FrameOffset(48), FrameOffset(44), scratch_register);
  __ GetCurrentThread(method_register);
  __ GetCurrentThread(FrameOffset(48), scratch_register);
  __ Move(scratch_register, method_register, 4);
  __ VerifyObject(scratch_register, false);

  __ CreateHandleScopeEntry(scratch_register, FrameOffset(48), scratch_register, true);
  __ CreateHandleScopeEntry(scratch_register, FrameOffset(48), scratch_register, false);
  __ CreateHandleScopeEntry(method_register, FrameOffset(48), scratch_register, true);
  __ CreateHandleScopeEntry(FrameOffset(48), FrameOffset(64), scratch_register, true);
  __ CreateHandleScopeEntry(method_register, FrameOffset(0), scratch_register, true);
  __ CreateHandleScopeEntry(method_register, FrameOffset(1025), scratch_register, true);
  __ CreateHandleScopeEntry(scratch_register, FrameOffset(1025), scratch_register, true);

  __ ExceptionPoll(scratch_register, 0);

  // Push the target out of range of branch emitted by ExceptionPoll.
  for (int i = 0; i < 64; i++) {
    __ Store(FrameOffset(2047), scratch_register, 4);
  }

  __ DecreaseFrameSize(4096);
  __ DecreaseFrameSize(32);
  __ RemoveFrame(frame_size, callee_save_regs, /* may_suspend= */ true);

  EmitAndCheck(&assembler, "VixlJniHelpers");
}

#undef __

// TODO: Avoid these macros.
#define R0 vixl::aarch32::r0
#define R2 vixl::aarch32::r2
#define R4 vixl::aarch32::r4
#define R12 vixl::aarch32::r12

#define __ assembler.asm_.

TEST_F(ArmVIXLAssemblerTest, VixlLoadFromOffset) {
  __ LoadFromOffset(kLoadWord, R2, R4, 12);
  __ LoadFromOffset(kLoadWord, R2, R4, 0xfff);
  __ LoadFromOffset(kLoadWord, R2, R4, 0x1000);
  __ LoadFromOffset(kLoadWord, R2, R4, 0x1000a4);
  __ LoadFromOffset(kLoadWord, R2, R4, 0x101000);
  __ LoadFromOffset(kLoadWord, R4, R4, 0x101000);
  __ LoadFromOffset(kLoadUnsignedHalfword, R2, R4, 12);
  __ LoadFromOffset(kLoadUnsignedHalfword, R2, R4, 0xfff);
  __ LoadFromOffset(kLoadUnsignedHalfword, R2, R4, 0x1000);
  __ LoadFromOffset(kLoadUnsignedHalfword, R2, R4, 0x1000a4);
  __ LoadFromOffset(kLoadUnsignedHalfword, R2, R4, 0x101000);
  __ LoadFromOffset(kLoadUnsignedHalfword, R4, R4, 0x101000);
  __ LoadFromOffset(kLoadWordPair, R2, R4, 12);
  __ LoadFromOffset(kLoadWordPair, R2, R4, 0x3fc);
  __ LoadFromOffset(kLoadWordPair, R2, R4, 0x400);
  __ LoadFromOffset(kLoadWordPair, R2, R4, 0x400a4);
  __ LoadFromOffset(kLoadWordPair, R2, R4, 0x40400);
  __ LoadFromOffset(kLoadWordPair, R4, R4, 0x40400);

  vixl::aarch32::UseScratchRegisterScope temps(assembler.asm_.GetVIXLAssembler());
  temps.Exclude(R12);
  __ LoadFromOffset(kLoadWord, R0, R12, 12);  // 32-bit because of R12.
  temps.Include(R12);
  __ LoadFromOffset(kLoadWord, R2, R4, 0xa4 - 0x100000);

  __ LoadFromOffset(kLoadSignedByte, R2, R4, 12);
  __ LoadFromOffset(kLoadUnsignedByte, R2, R4, 12);
  __ LoadFromOffset(kLoadSignedHalfword, R2, R4, 12);

  EmitAndCheck(&assembler, "VixlLoadFromOffset");
}

TEST_F(ArmVIXLAssemblerTest, VixlStoreToOffset) {
  __ StoreToOffset(kStoreWord, R2, R4, 12);
  __ StoreToOffset(kStoreWord, R2, R4, 0xfff);
  __ StoreToOffset(kStoreWord, R2, R4, 0x1000);
  __ StoreToOffset(kStoreWord, R2, R4, 0x1000a4);
  __ StoreToOffset(kStoreWord, R2, R4, 0x101000);
  __ StoreToOffset(kStoreWord, R4, R4, 0x101000);
  __ StoreToOffset(kStoreHalfword, R2, R4, 12);
  __ StoreToOffset(kStoreHalfword, R2, R4, 0xfff);
  __ StoreToOffset(kStoreHalfword, R2, R4, 0x1000);
  __ StoreToOffset(kStoreHalfword, R2, R4, 0x1000a4);
  __ StoreToOffset(kStoreHalfword, R2, R4, 0x101000);
  __ StoreToOffset(kStoreHalfword, R4, R4, 0x101000);
  __ StoreToOffset(kStoreWordPair, R2, R4, 12);
  __ StoreToOffset(kStoreWordPair, R2, R4, 0x3fc);
  __ StoreToOffset(kStoreWordPair, R2, R4, 0x400);
  __ StoreToOffset(kStoreWordPair, R2, R4, 0x400a4);
  __ StoreToOffset(kStoreWordPair, R2, R4, 0x40400);
  __ StoreToOffset(kStoreWordPair, R4, R4, 0x40400);

  vixl::aarch32::UseScratchRegisterScope temps(assembler.asm_.GetVIXLAssembler());
  temps.Exclude(R12);
  __ StoreToOffset(kStoreWord, R0, R12, 12);  // 32-bit because of R12.
  temps.Include(R12);
  __ StoreToOffset(kStoreWord, R2, R4, 0xa4 - 0x100000);

  __ StoreToOffset(kStoreByte, R2, R4, 12);

  EmitAndCheck(&assembler, "VixlStoreToOffset");
}

#undef __
}  // namespace arm
}  // namespace art