// AsmJit - Machine code generation for C++
//
//  * Official AsmJit Home Page: https://asmjit.com
//  * Official Github Repository: https://github.com/asmjit/asmjit
//
// Copyright (c) 2008-2020 The AsmJit Authors
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
//    claim that you wrote the original software. If you use this software
//    in a product, an acknowledgment in the product documentation would be
//    appreciated but is not required.
// 2. Altered source versions must be plainly marked as such, and must not be
//    misrepresented as being the original software.
// 3. This notice may not be removed or altered from any source distribution.

#include <asmjit/core.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <memory>
#include <vector>

#include "cmdline.h"
#include "asmjit_test_compiler.h"

#if defined(ASMJIT_BUILD_X86) && ASMJIT_ARCH_X86
#include <asmjit/x86.h>
void compiler_add_x86_tests(TestApp& app);
#endif

#if defined(ASMJIT_BUILD_ARM) && ASMJIT_ARCH_ARM == 64
#include <asmjit/a64.h>
void compiler_add_a64_tests(TestApp& app);
#endif

#if defined(ASMJIT_BUILD_X86) && ASMJIT_ARCH_X86
  #define ASMJIT_HAVE_WORKING_JIT
#endif

#if defined(ASMJIT_BUILD_ARM) && ASMJIT_ARCH_ARM == 64
  #define ASMJIT_HAVE_WORKING_JIT
#endif

using namespace asmjit;

// ============================================================================
// [TestApp]
// ============================================================================

static const char* archAsString(uint32_t arch) {
  switch (arch) {
    case Environment::kArchX86: return "X86";
    case Environment::kArchX64: return "X64";
    case Environment::kArchARM: return "A32";
    case Environment::kArchThumb: return "T32";
    case Environment::kArchAArch64: return "A64";
    default: return "Unknown";
  }
}

int TestApp::handleArgs(int argc, const char* const* argv) {
  CmdLine cmd(argc, argv);

  if (cmd.hasArg("--verbose")) _verbose = true;
  if (cmd.hasArg("--dump-asm")) _dumpAsm = true;
  if (cmd.hasArg("--dump-hex")) _dumpHex = true;

  return 0;
}

void TestApp::showInfo() {
  printf("AsmJit Compiler Test-Suite v%u.%u.%u  [Arch=%s]:\n",
    unsigned((ASMJIT_LIBRARY_VERSION >> 16)       ),
    unsigned((ASMJIT_LIBRARY_VERSION >>  8) & 0xFF),
    unsigned((ASMJIT_LIBRARY_VERSION      ) & 0xFF),
    archAsString(Environment::kArchHost));
  printf("  [%s] Verbose (use --verbose to turn verbose output ON)\n", _verbose ? "x" : " ");
  printf("  [%s] DumpAsm (use --dump-asm to turn assembler dumps ON)\n", _dumpAsm ? "x" : " ");
  printf("  [%s] DumpHex (use --dump-hex to dump binary in hexadecimal)\n", _dumpHex ? "x" : " ");
  printf("\n");
}

int TestApp::run() {
#ifndef ASMJIT_HAVE_WORKING_JIT
  return 0;
#else
#ifndef ASMJIT_NO_LOGGING
  uint32_t kFormatFlags = FormatOptions::kFlagMachineCode   |
                          FormatOptions::kFlagExplainImms   |
                          FormatOptions::kFlagRegCasts      |
                          FormatOptions::kFlagAnnotations   |
                          FormatOptions::kFlagDebugPasses   |
                          FormatOptions::kFlagDebugRA       ;

  FileLogger fileLogger(stdout);
  fileLogger.addFlags(kFormatFlags);

  StringLogger stringLogger;
  stringLogger.addFlags(kFormatFlags);
#endif

  for (std::unique_ptr<TestCase>& test : _tests) {
    JitRuntime runtime;
    CodeHolder code;
    SimpleErrorHandler errorHandler;

    code.init(runtime.environment());
    code.setErrorHandler(&errorHandler);

#ifndef ASMJIT_NO_LOGGING
    if (_verbose) {
      code.setLogger(&fileLogger);
    }
    else {
      stringLogger.clear();
      code.setLogger(&stringLogger);
    }
#endif

    printf("[Test] %s", test->name());

#ifndef ASMJIT_NO_LOGGING
    if (_verbose) printf("\n");
#endif

#if defined(ASMJIT_BUILD_X86) && ASMJIT_ARCH_X86
    x86::Compiler cc(&code);
#endif

#if defined(ASMJIT_BUILD_ARM) && ASMJIT_ARCH_ARM == 64
    arm::Compiler cc(&code);
#endif

    test->compile(cc);

    void* func = nullptr;
    Error err = errorHandler._err;

    if (!err)
      err = cc.finalize();

#ifndef ASMJIT_NO_LOGGING
    if (_dumpAsm) {
      if (!_verbose) printf("\n");

      String sb;
      Formatter::formatNodeList(sb, kFormatFlags, &cc);
      printf("%s", sb.data());
    }
#endif

    if (err == kErrorOk)
      err = runtime.add(&func, &code);

    if (err == kErrorOk && _dumpHex) {
      String sb;
      sb.appendHex((void*)func, code.codeSize());
      printf("\n (HEX: %s)\n", sb.data());
    }

    if (_verbose)
      fflush(stdout);

    if (err == kErrorOk) {
      _outputSize += code.codeSize();

      StringTmp<128> result;
      StringTmp<128> expect;

      if (test->run(func, result, expect)) {
        if (!_verbose) printf(" [OK]\n");
      }
      else {
        if (!_verbose) printf(" [FAILED]\n");

#ifndef ASMJIT_NO_LOGGING
        if (!_verbose) printf("%s", stringLogger.data());
#endif

        printf("[Status]\n");
        printf("  Returned: %s\n", result.data());
        printf("  Expected: %s\n", expect.data());

        _nFailed++;
      }

      if (_dumpAsm)
        printf("\n");

      runtime.release(func);
    }
    else {
      if (!_verbose) printf(" [FAILED]\n");

#ifndef ASMJIT_NO_LOGGING
      if (!_verbose) printf("%s", stringLogger.data());
#endif

      printf("[Status]\n");
      printf("  ERROR 0x%08X: %s\n", unsigned(err), errorHandler._message.data());

      _nFailed++;
    }
  }

  if (_nFailed == 0)
    printf("\nSuccess:\n  All %u tests passed\n", unsigned(_tests.size()));
  else
    printf("\nFailure:\n  %u %s of %u failed\n", _nFailed, _nFailed == 1 ? "test" : "tests", unsigned(_tests.size()));

  printf("  OutputSize=%zu\n", _outputSize);
  printf("\n");

  return _nFailed == 0 ? 0 : 1;
#endif
}

// ============================================================================
// [Main]
// ============================================================================

int main(int argc, char* argv[]) {
  TestApp app;

  app.handleArgs(argc, argv);
  app.showInfo();

#if defined(ASMJIT_BUILD_X86) && ASMJIT_ARCH_X86
  compiler_add_x86_tests(app);
#endif

#if defined(ASMJIT_BUILD_ARM) && ASMJIT_ARCH_ARM == 64
  compiler_add_a64_tests(app);
#endif

  return app.run();
}
