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

#include "test/cctest/cctest.h"

#include "src/assembler-inl.h"
#include "src/handles-inl.h"
#include "src/isolate.h"
#include "src/macro-assembler-inl.h"
#include "src/simulator.h"
#include "src/snapshot/macros.h"
#include "src/snapshot/snapshot.h"

// To generate the binary files for the test function, enable this section and
// run GenerateTestFunctionData once on each arch.
#define GENERATE_TEST_FUNCTION_DATA false

namespace v8 {
namespace internal {
namespace test_isolate_independent_builtins {

#ifdef V8_EMBEDDED_BUILTINS
UNINITIALIZED_TEST(VerifyBuiltinsIsolateIndependence) {
  v8::Isolate::CreateParams create_params;
  create_params.array_buffer_allocator = CcTest::array_buffer_allocator();
  v8::Isolate* v8_isolate = v8::Isolate::New(create_params);

  {
    v8::Isolate::Scope isolate_scope(v8_isolate);
    v8::internal::Isolate* isolate =
        reinterpret_cast<v8::internal::Isolate*>(v8_isolate);
    HandleScope handle_scope(isolate);

    Snapshot::EnsureAllBuiltinsAreDeserialized(isolate);

    // TODO(jgruber,v8:6666): Investigate CONST_POOL and VENEER_POOL kinds.
    // CONST_POOL is currently relevant on {arm,arm64,mips,mips64,ppc,s390}.
    //            Rumors are it will also become relevant on x64. My
    //            understanding is that we should be fine if we ensure it
    //            doesn't contain heap constants and we use pc-relative
    //            addressing.
    // VENEER_POOL is arm64-only. From what I've seen, jumps are pc-relative
    //             and stay within the same code object and thus should be
    //             isolate-independent.

    // Build a white-list of all isolate-independent RelocInfo entry kinds.
    constexpr int all_real_modes_mask =
        (1 << (RelocInfo::LAST_REAL_RELOC_MODE + 1)) - 1;
    constexpr int mode_mask =
        all_real_modes_mask & ~RelocInfo::ModeMask(RelocInfo::COMMENT) &
        ~RelocInfo::ModeMask(RelocInfo::INTERNAL_REFERENCE) &
        ~RelocInfo::ModeMask(RelocInfo::INTERNAL_REFERENCE_ENCODED) &
        ~RelocInfo::ModeMask(RelocInfo::OFF_HEAP_TARGET) &
        ~RelocInfo::ModeMask(RelocInfo::CONST_POOL) &
        ~RelocInfo::ModeMask(RelocInfo::VENEER_POOL);
    STATIC_ASSERT(RelocInfo::LAST_REAL_RELOC_MODE == RelocInfo::VENEER_POOL);
    STATIC_ASSERT(RelocInfo::ModeMask(RelocInfo::COMMENT) ==
                  (1 << RelocInfo::COMMENT));
    STATIC_ASSERT(
        mode_mask ==
        (RelocInfo::ModeMask(RelocInfo::CODE_TARGET) |
         RelocInfo::ModeMask(RelocInfo::EMBEDDED_OBJECT) |
         RelocInfo::ModeMask(RelocInfo::WASM_GLOBAL_HANDLE) |
         RelocInfo::ModeMask(RelocInfo::WASM_CALL) |
         RelocInfo::ModeMask(RelocInfo::JS_TO_WASM_CALL) |
         RelocInfo::ModeMask(RelocInfo::RUNTIME_ENTRY) |
         RelocInfo::ModeMask(RelocInfo::EXTERNAL_REFERENCE)));

    constexpr bool kVerbose = false;
    bool found_mismatch = false;
    for (int i = 0; i < Builtins::builtin_count; i++) {
      Code* code = isolate->builtins()->builtin(i);

      bool is_isolate_independent = true;
      for (RelocIterator it(code, mode_mask); !it.done(); it.next()) {
        if (kVerbose) {
          if (is_isolate_independent) {
            printf("%s %s\n", Builtins::KindNameOf(i),
                   isolate->builtins()->name(i));
          }
#ifdef ENABLE_DISASSEMBLER
          RelocInfo::Mode mode = it.rinfo()->rmode();
          printf("  %s\n", RelocInfo::RelocModeName(mode));
#endif
        }
        is_isolate_independent = false;
      }

      // Relaxed condition only checks whether the isolate-independent list is
      // valid, not whether it is complete. This is to avoid constant work
      // updating the list.
      bool should_be_isolate_independent = Builtins::IsIsolateIndependent(i);
      if (should_be_isolate_independent && !is_isolate_independent) {
        found_mismatch = true;
        printf("%s %s expected: %d, is: %d\n", Builtins::KindNameOf(i),
               isolate->builtins()->name(i), should_be_isolate_independent,
               is_isolate_independent);
      }
    }

    CHECK(!found_mismatch);
  }

  v8_isolate->Dispose();
}
#endif  // V8_EMBEDDED_BUILTINS

// V8_CC_MSVC is true for both MSVC and clang on windows. clang can handle
// __asm__-style inline assembly but MSVC cannot, and thus we need a more
// precise compiler detection that can distinguish between the two. clang on
// windows sets both __clang__ and _MSC_VER, MSVC sets only _MSC_VER.
#if defined(_MSC_VER) && !defined(__clang__)
#define V8_COMPILER_IS_MSVC
#endif

#ifndef V8_COMPILER_IS_MSVC
#if GENERATE_TEST_FUNCTION_DATA

// Arch-specific defines.
#if V8_TARGET_ARCH_IA32
#define TEST_FUNCTION_FILE "f-ia32.bin"
#elif V8_TARGET_ARCH_X64 && _WIN64
#define TEST_FUNCTION_FILE "f-x64-win.bin"
#elif V8_TARGET_ARCH_X64
#define TEST_FUNCTION_FILE "f-x64.bin"
#elif V8_TARGET_ARCH_ARM64
#define TEST_FUNCTION_FILE "f-arm64.bin"
#elif V8_TARGET_ARCH_ARM
#define TEST_FUNCTION_FILE "f-arm.bin"
#elif V8_TARGET_ARCH_PPC
#define TEST_FUNCTION_FILE "f-ppc.bin"
#elif V8_TARGET_ARCH_MIPS
#define TEST_FUNCTION_FILE "f-mips.bin"
#elif V8_TARGET_ARCH_MIPS64
#define TEST_FUNCTION_FILE "f-mips64.bin"
#elif V8_TARGET_ARCH_S390
#define TEST_FUNCTION_FILE "f-s390.bin"
#else
#error "Unknown architecture."
#endif

#define __ masm.

TEST(GenerateTestFunctionData) {
  CcTest::InitializeVM();
  Isolate* isolate = CcTest::i_isolate();
  HandleScope scope(isolate);

#if V8_TARGET_ARCH_IA32
  v8::internal::byte buffer[256];
  Assembler masm(isolate, buffer, sizeof(buffer));

  __ mov(eax, Operand(esp, 4));
  __ add(eax, Operand(esp, 8));
  __ ret(0);
#elif V8_TARGET_ARCH_X64
  size_t allocated;
  byte* buffer = AllocateAssemblerBuffer(&allocated);
  Assembler masm(isolate, buffer, static_cast<int>(allocated));

#ifdef _WIN64
  static const Register arg1 = rcx;
  static const Register arg2 = rdx;
#else
  static const Register arg1 = rdi;
  static const Register arg2 = rsi;
#endif

  __ movq(rax, arg2);
  __ addq(rax, arg1);
  __ ret(0);
#elif V8_TARGET_ARCH_ARM64
  MacroAssembler masm(isolate, nullptr, 0,
                      v8::internal::CodeObjectRequired::kYes);

  __ Add(x0, x0, x1);
  __ Ret();
#elif V8_TARGET_ARCH_ARM
  Assembler masm(isolate, nullptr, 0);

  __ add(r0, r0, Operand(r1));
  __ mov(pc, Operand(lr));
#elif V8_TARGET_ARCH_PPC
  Assembler masm(isolate, nullptr, 0);

  __ function_descriptor();
  __ add(r3, r3, r4);
  __ blr();
#elif V8_TARGET_ARCH_MIPS
  MacroAssembler masm(isolate, nullptr, 0,
                      v8::internal::CodeObjectRequired::kYes);

  __ addu(v0, a0, a1);
  __ jr(ra);
  __ nop();
#elif V8_TARGET_ARCH_MIPS64
  MacroAssembler masm(isolate, nullptr, 0,
                      v8::internal::CodeObjectRequired::kYes);

  __ addu(v0, a0, a1);
  __ jr(ra);
  __ nop();
#elif V8_TARGET_ARCH_S390
  Assembler masm(isolate, nullptr, 0);

  __ agr(r2, r3);
  __ b(r14);
#else  // Unknown architecture.
#error "Unknown architecture."
#endif  // Target architecture.

  CodeDesc desc;
  masm.GetCode(isolate, &desc);

  std::ofstream of(TEST_FUNCTION_FILE, std::ios::out | std::ios::binary);
  of.write(reinterpret_cast<char*>(desc.buffer), desc.instr_size);
}
#undef __
#endif  // GENERATE_TEST_FUNCTION_DATA

#if V8_TARGET_ARCH_IA32
#define FUNCTION_BYTES \
  ".byte 0x8b, 0x44, 0x24, 0x04, 0x03, 0x44, 0x24, 0x08, 0xc3\n"
#elif V8_TARGET_ARCH_X64 && _WIN64
#define FUNCTION_BYTES ".byte 0x48, 0x8b, 0xc2, 0x48, 0x03, 0xc1, 0xc3\n"
#elif V8_TARGET_ARCH_X64
#define FUNCTION_BYTES ".byte 0x48, 0x8b, 0xc6, 0x48, 0x03, 0xc7, 0xc3\n"
#elif V8_TARGET_ARCH_ARM64
#define FUNCTION_BYTES ".byte 0x00, 0x00, 0x01, 0x8b, 0xc0, 0x03, 0x5f, 0xd6\n"
#elif V8_TARGET_ARCH_ARM
#define FUNCTION_BYTES ".byte 0x01, 0x00, 0x80, 0xe0, 0x0e, 0xf0, 0xa0, 0xe1\n"
#elif V8_TARGET_ARCH_PPC
#if defined(V8_OS_AIX)
#define FUNCTION_BYTES ".byte 0x7c, 0x64, 0x1a, 0x14, 0x4e, 0x80, 0x00, 0x20\n"
#else
#define FUNCTION_BYTES ".byte 0x14, 0x22, 0x63, 0x7c, 0x20, 0x00, 0x80, 0x4e\n"
#endif
#elif defined(V8_TARGET_ARCH_MIPS) || defined(V8_TARGET_ARCH_MIPS64)
#if defined(V8_TARGET_BIG_ENDIAN)
#define FUNCTION_BYTES                               \
  ".byte 0x00, 0x85, 0x10, 0x21, 0x03, 0xe0, 0x00, " \
  "0x08, 0x00, 0x00, 0x00, 0x00\n"
#else
#define FUNCTION_BYTES                               \
  ".byte 0x21, 0x10, 0x85, 0x00, 0x08, 0x00, 0xe0, " \
  "0x03, 0x00, 0x00, 0x00, 0x00\n"
#endif
#elif V8_TARGET_ARCH_S390
#define FUNCTION_BYTES                               \
  ".byte 0xb9, 0x08, 0x00, 0x23, 0x07, 0xfe\n"
#else
#error "Unknown architecture."
#endif

V8_EMBEDDED_RODATA_HEADER(test_string0_bytes)
__asm__(".byte 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37\n"
        ".byte 0x38, 0x39, 0x0a, 0x00\n");
extern "C" V8_ALIGNED(16) const char test_string0_bytes[];

V8_EMBEDDED_TEXT_HEADER(test_function0_bytes)
__asm__(FUNCTION_BYTES);
extern "C" V8_ALIGNED(16) const char test_function0_bytes[];
// clang-format on

// A historical note: We use .byte over .incbin since the latter leads to
// complications involving generation of build-time dependencies.  Goma parses
// #include statements, and clang has -MD/-MMD. Neither recognize .incbin.

TEST(ByteInRodata) {
  CHECK_EQ(0, std::strcmp("0123456789\n", test_string0_bytes));
}

TEST(ByteInText) {
  CcTest::InitializeVM();
  Isolate* isolate = CcTest::i_isolate();
  auto f = GeneratedCode<int(int, int)>::FromAddress(
      isolate, reinterpret_cast<Address>(&test_function0_bytes[0]));
  CHECK_EQ(7, f.Call(3, 4));
  CHECK_EQ(11, f.Call(5, 6));
}
#endif  // #ifndef V8_COMPILER_IS_MSVC
#undef V8_COMPILER_IS_MSVC

#undef FUNCTION_BYTES
#undef GENERATE_TEST_FUNCTION_DATA
#undef TEST_FUNCTION_FILE

}  // namespace test_isolate_independent_builtins
}  // namespace internal
}  // namespace v8
