/*
 * Copyright (C) 2015 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 <memory>
#include <vector>

#include "arch/instruction_set.h"
#include "base/arena_allocator.h"
#include "base/enums.h"
#include "base/malloc_arena_pool.h"
#include "cfi_test.h"
#include "gtest/gtest.h"
#include "jni/quick/calling_convention.h"
#include "read_barrier_config.h"
#include "utils/assembler.h"
#include "utils/jni_macro_assembler.h"

#include "jni/jni_cfi_test_expected.inc"

namespace art {

// Run the tests only on host.
#ifndef ART_TARGET_ANDROID

class JNICFITest : public CFITest {
 public:
  // Enable this flag to generate the expected outputs.
  static constexpr bool kGenerateExpected = false;

  void TestImpl(InstructionSet isa,
                const char* isa_str,
                const std::vector<uint8_t>& expected_asm,
                const std::vector<uint8_t>& expected_cfi) {
    if (Is64BitInstructionSet(isa)) {
      TestImplSized<PointerSize::k64>(isa, isa_str, expected_asm, expected_cfi);
    } else {
      TestImplSized<PointerSize::k32>(isa, isa_str, expected_asm, expected_cfi);
    }
  }

 private:
  template <PointerSize kPointerSize>
  void TestImplSized(InstructionSet isa,
                     const char* isa_str,
                     const std::vector<uint8_t>& expected_asm,
                     const std::vector<uint8_t>& expected_cfi) {
    // Description of simple method.
    const bool is_static = true;
    const bool is_synchronized = false;
    const char* shorty = "IIFII";

    MallocArenaPool pool;
    ArenaAllocator allocator(&pool);

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

    // Assemble the method.
    std::unique_ptr<JNIMacroAssembler<kPointerSize>> jni_asm(
        JNIMacroAssembler<kPointerSize>::Create(&allocator, isa));
    jni_asm->cfi().SetEnabled(true);
    jni_asm->BuildFrame(frame_size, mr_conv->MethodRegister(),
                        callee_save_regs, mr_conv->EntrySpills());
    jni_asm->IncreaseFrameSize(32);
    jni_asm->DecreaseFrameSize(32);
    jni_asm->RemoveFrame(frame_size, callee_save_regs, /* may_suspend= */ true);
    jni_asm->FinalizeCode();
    std::vector<uint8_t> actual_asm(jni_asm->CodeSize());
    MemoryRegion code(&actual_asm[0], actual_asm.size());
    jni_asm->FinalizeInstructions(code);
    ASSERT_EQ(jni_asm->cfi().GetCurrentCFAOffset(), frame_size);
    const std::vector<uint8_t>& actual_cfi = *(jni_asm->cfi().data());

    if (kGenerateExpected) {
      GenerateExpected(stdout,
                       isa,
                       isa_str,
                       ArrayRef<const uint8_t>(actual_asm),
                       ArrayRef<const uint8_t>(actual_cfi));
    } else {
      EXPECT_EQ(expected_asm, actual_asm);
      EXPECT_EQ(expected_cfi, actual_cfi);
    }
  }
};

#define TEST_ISA(isa)                                                 \
  TEST_F(JNICFITest, isa) {                                           \
    std::vector<uint8_t> expected_asm(expected_asm_##isa,             \
        expected_asm_##isa + arraysize(expected_asm_##isa));          \
    std::vector<uint8_t> expected_cfi(expected_cfi_##isa,             \
        expected_cfi_##isa + arraysize(expected_cfi_##isa));          \
    TestImpl(InstructionSet::isa, #isa, expected_asm, expected_cfi);  \
  }

#ifdef ART_ENABLE_CODEGEN_arm
// Run the tests for ARM only with Baker read barriers, as the
// expected generated code contains a Marking Register refresh
// instruction.
#if defined(USE_READ_BARRIER) && defined(USE_BAKER_READ_BARRIER)
TEST_ISA(kThumb2)
#endif
#endif

#ifdef ART_ENABLE_CODEGEN_arm64
// Run the tests for ARM64 only with Baker read barriers, as the
// expected generated code contains a Marking Register refresh
// instruction.
#if defined(USE_READ_BARRIER) && defined(USE_BAKER_READ_BARRIER)
TEST_ISA(kArm64)
#endif
#endif

#ifdef ART_ENABLE_CODEGEN_x86
TEST_ISA(kX86)
#endif

#ifdef ART_ENABLE_CODEGEN_x86_64
TEST_ISA(kX86_64)
#endif

#endif  // ART_TARGET_ANDROID

}  // namespace art
