/*
 * Copyright (c) 2021 Huawei Device Co., Ltd.
 * 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 "ecmascript/compiler/assembler/aarch64/assembler_aarch64.h"

#include <ostream>
#include <sstream>

#include "ecmascript/ecma_vm.h"
#include "ecmascript/mem/dyn_chunk.h"
#include "ecmascript/tests/test_helper.h"

#include "llvm-c/Analysis.h"
#include "llvm-c/Core.h"
#include "llvm-c/Disassembler.h"
#include "llvm-c/ExecutionEngine.h"
#include "llvm-c/Target.h"

namespace panda::test {
using namespace panda::ecmascript;
using namespace panda::ecmascript::aarch64;
class AssemblerAarch64Test : public testing::Test {
public:
    static void SetUpTestCase()
    {
        GTEST_LOG_(INFO) << "SetUpTestCase";
    }

    static void TearDownTestCase()
    {
        GTEST_LOG_(INFO) << "TearDownCase";
    }

    void SetUp() override
    {
        InitializeLLVM(TARGET_AARCH64);
        TestHelper::CreateEcmaVMWithScope(instance, thread, scope);
        chunk_ = thread->GetEcmaVM()->GetChunk();
    }

    void TearDown() override
    {
        TestHelper::DestroyEcmaVMWithScope(instance, scope);
    }

    static const char *SymbolLookupCallback([[maybe_unused]] void *disInfo, [[maybe_unused]] uint64_t referenceValue,
                                            uint64_t *referenceType, [[maybe_unused]] uint64_t referencePC,
                                            [[maybe_unused]] const char **referenceName)
    {
        *referenceType = LLVMDisassembler_ReferenceType_InOut_None;
        return nullptr;
    }

    void InitializeLLVM(std::string triple)
    {
        if (triple.compare(TARGET_X64) == 0) {
            LLVMInitializeX86TargetInfo();
            LLVMInitializeX86TargetMC();
            LLVMInitializeX86Disassembler();
            /* this method must be called, ohterwise "Target does not support MC emission" */
            LLVMInitializeX86AsmPrinter();
            LLVMInitializeX86AsmParser();
            LLVMInitializeX86Target();
        } else if (triple.compare(TARGET_AARCH64) == 0) {
            LLVMInitializeAArch64TargetInfo();
            LLVMInitializeAArch64TargetMC();
            LLVMInitializeAArch64Disassembler();
            LLVMInitializeAArch64AsmPrinter();
            LLVMInitializeAArch64AsmParser();
            LLVMInitializeAArch64Target();
        } else {
            LOG_ECMA(FATAL) << "this branch is unreachable";
            UNREACHABLE();
        }
    }

    void DisassembleChunk(const char *triple, Assembler *assemlber, std::ostream &os)
    {
        LLVMDisasmContextRef dcr = LLVMCreateDisasm(triple, nullptr, 0, nullptr, SymbolLookupCallback);
        uint8_t *byteSp = assemlber->GetBegin();
        size_t numBytes = assemlber->GetCurrentPosition();
        unsigned pc = 0;
        const char outStringSize = 100;
        char outString[outStringSize];
        while (numBytes > 0) {
            size_t InstSize = LLVMDisasmInstruction(dcr, byteSp, numBytes, pc, outString, outStringSize);
            if (InstSize == 0) {
                // 8 : 8 means width of the pc offset and instruction code
                os << std::setw(8) << std::setfill('0') << std::hex << pc << ":" << std::setw(8)
                   << *reinterpret_cast<uint32_t *>(byteSp) << "maybe constant" << std::endl;
                pc += 4; // 4 pc length
                byteSp += 4; // 4 sp offset
                numBytes -= 4; // 4 num bytes
            }
            // 8 : 8 means width of the pc offset and instruction code
            os << std::setw(8) << std::setfill('0') << std::hex << pc << ":" << std::setw(8)
               << *reinterpret_cast<uint32_t *>(byteSp) << " " << outString << std::endl;
            pc += InstSize;
            byteSp += InstSize;
            numBytes -= InstSize;
        }
        LLVMDisasmDispose(dcr);
    }
    EcmaVM *instance {nullptr};
    JSThread *thread {nullptr};
    EcmaHandleScope *scope {nullptr};
    Chunk *chunk_ {nullptr};
};

#define __ masm.
HWTEST_F_L0(AssemblerAarch64Test, Mov)
{
    std::string expectResult("00000000:d28acf01 \tmov\tx1, #22136\n"
                             "00000004:f2a24681 \tmovk\tx1, #4660, lsl #16\n"
                             "00000008:f2ffffe1 \tmovk\tx1, #65535, lsl #48\n"
                             "0000000c:d2801de2 \tmov\tx2, #239\n"
                             "00000010:f2b579a2 \tmovk\tx2, #43981, lsl #16\n"
                             "00000014:f2cacf02 \tmovk\tx2, #22136, lsl #32\n"
                             "00000018:f2e24682 \tmovk\tx2, #4660, lsl #48\n"
                             "0000001c:b2683be3 \tmov\tx3, #549739036672\n"
                             "00000020:f2824683 \tmovk\tx3, #4660\n"
                             "00000024:32083fe4 \tmov\tw4, #-16776961\n");
    AssemblerAarch64 masm(chunk_);
    __ Mov(x1,  Immediate(0xffff000012345678));
    __ Mov(x2,  Immediate(0x12345678abcd00ef));
    __ Mov(x3,  Immediate(0x7fff001234));
    __ Mov(w4,  Immediate(0xff0000ff));
    std::ostringstream oss;
    DisassembleChunk(TARGET_AARCH64, &masm, oss);
    ASSERT_EQ(oss.str(), expectResult);
}

HWTEST_F_L0(AssemblerAarch64Test, MovReg)
{
    std::string expectResult("00000000:aa0203e1 \tmov\tx1, x2\n"
                             "00000004:910003e2 \tmov\tx2, sp\n"
                             "00000008:2a0203e1 \tmov\tw1, w2\n");
    AssemblerAarch64 masm(chunk_);
    __ Mov(x1,  x2);
    __ Mov(x2,  sp);
    __ Mov(w1,  w2);
    std::ostringstream oss;
    DisassembleChunk(TARGET_AARCH64, &masm, oss);
    ASSERT_EQ(oss.str(), expectResult);
}

HWTEST_F_L0(AssemblerAarch64Test, LdpStp)
{
    std::string expectResult("00000000:a8808be1 \tstp\tx1, x2, [sp], #8\n"
                             "00000004:a9c08be1 \tldp\tx1, x2, [sp, #8]!\n"
                             "00000008:a94093e3 \tldp\tx3, x4, [sp, #8]\n"
                             "0000000c:294113e3 \tldp\tw3, w4, [sp, #8]\n");

    AssemblerAarch64 masm(chunk_);
    __ Stp(x1,  x2, MemoryOperand(sp, 8, POSTINDEX));
    __ Ldp(x1,  x2, MemoryOperand(sp, 8, PREINDEX));
    __ Ldp(x3,  x4, MemoryOperand(sp, 8, OFFSET));
    __ Ldp(w3,  w4, MemoryOperand(sp, 8, OFFSET));
    std::ostringstream oss;
    DisassembleChunk(TARGET_AARCH64, &masm, oss);
    ASSERT_EQ(oss.str(), expectResult);
}

HWTEST_F_L0(AssemblerAarch64Test, LdrStr)
{
    std::string expectResult("00000000:f80087e1 \tstr\tx1, [sp], #8\n"
                             "00000004:f81f87e1 \tstr\tx1, [sp], #-8\n"
                             "00000008:f8408fe1 \tldr\tx1, [sp, #8]!\n"
                             "0000000c:f94007e3 \tldr\tx3, [sp, #8]\n"
                             "00000010:b9400be3 \tldr\tw3, [sp, #8]\n"
                             "00000014:38408fe1 \tldrb\tw1, [sp, #8]!\n"
                             "00000018:394023e1 \tldrb\tw1, [sp, #8]\n"
                             "0000001c:78408fe1 \tldrh\tw1, [sp, #8]!\n"
                             "00000020:794013e1 \tldrh\tw1, [sp, #8]\n"
                             "00000024:f85f83e1 \tldur\tx1, [sp, #-8]\n"
                             "00000028:f81f83e3 \tstur\tx3, [sp, #-8]\n");

    AssemblerAarch64 masm(chunk_);
    __ Str(x1, MemoryOperand(sp, 8, POSTINDEX));
    __ Str(x1, MemoryOperand(sp, -8, POSTINDEX));
    __ Ldr(x1, MemoryOperand(sp, 8, PREINDEX));
    __ Ldr(x3, MemoryOperand(sp, 8, OFFSET));
    __ Ldr(w3, MemoryOperand(sp, 8, OFFSET));
    __ Ldrb(w1, MemoryOperand(sp, 8, PREINDEX));
    __ Ldrb(w1, MemoryOperand(sp, 8, OFFSET));
    __ Ldrh(w1, MemoryOperand(sp, 8, PREINDEX));
    __ Ldrh(w1, MemoryOperand(sp, 8, OFFSET));
    __ Ldur(x1, MemoryOperand(sp, -8, OFFSET));
    __ Stur(x3, MemoryOperand(sp, -8, OFFSET));
    std::ostringstream oss;
    DisassembleChunk(TARGET_AARCH64, &masm, oss);
    ASSERT_EQ(oss.str(), expectResult);
}

HWTEST_F_L0(AssemblerAarch64Test, AddSub)
{
    std::string expectResult("00000000:910023ff \tadd\tsp, sp, #8\n"
                             "00000004:d10023ff \tsub\tsp, sp, #8\n"
                             "00000008:8b020021 \tadd\tx1, x1, x2\n"
                             "0000000c:8b030c41 \tadd\tx1, x2, x3, lsl #3\n"
                             "00000010:8b234c41 \tadd\tx1, x2, w3, uxtw #3\n"
                             "00000014:8b224fff \tadd\tsp, sp, w2, uxtw #3\n");
    AssemblerAarch64 masm(chunk_);
    __ Add(sp, sp, Immediate(8));
    __ Add(sp, sp, Immediate(-8));
    __ Add(x1, x1, Operand(x2));
    __ Add(x1, x2, Operand(x3, LSL, 3));
    __ Add(x1, x2, Operand(x3, UXTW, 3));
    __ Add(sp, sp, Operand(x2, UXTW, 3));

    std::ostringstream oss;
    DisassembleChunk(TARGET_AARCH64, &masm, oss);
    ASSERT_EQ(oss.str(), expectResult);
}

HWTEST_F_L0(AssemblerAarch64Test, CMP)
{
    std::string expectResult("00000000:eb02003f \tcmp\tx1, x2\n"
                             "00000004:f100203f \tcmp\tx1, #8\n");
    AssemblerAarch64 masm(chunk_);
    __ Cmp(x1, x2);
    __ Cmp(x1, Immediate(8));

    std::ostringstream oss;
    DisassembleChunk(TARGET_AARCH64, &masm, oss);
    ASSERT_EQ(oss.str(), expectResult);
}

HWTEST_F_L0(AssemblerAarch64Test, Branch)
{
    std::string expectResult("00000000:eb02003f \tcmp\tx1, x2\n"
                             "00000004:54000080 \tb.eq\t0x14\n"
                             "00000008:f100203f \tcmp\tx1, #8\n"
                             "0000000c:5400004c \tb.gt\t0x14\n"
                             "00000010:14000002 \tb\t0x18\n"
                             "00000014:d2800140 \tmov\tx0, #10\n"
                             "00000018:b27f03e0 \torr\tx0, xzr, #0x2\n");
    AssemblerAarch64 masm(chunk_);
    Label label1;
    Label label2;
    __ Cmp(x1, x2);
    __ B(Condition::EQ, &label1);
    __ Cmp(x1, Immediate(8));
    __ B(Condition::GT, &label1);
    __ B(&label2);
    __ Bind(&label1);
    {
        __ Mov(x0, Immediate(0xa));
    }
    __ Bind(&label2);
    {
        __ Mov(x0, Immediate(0x2));
    }

    std::ostringstream oss;
    DisassembleChunk(TARGET_AARCH64, &masm, oss);
    ASSERT_EQ(oss.str(), expectResult);
}

HWTEST_F_L0(AssemblerAarch64Test, Loop)
{
    std::string expectResult("00000000:7100005f \tcmp\tw2, #0\n"
                             "00000004:540000e0 \tb.eq\t0x20\n"
                             "00000008:51000442 \tsub\tw2, w2, #1\n"
                             "0000000c:8b224c84 \tadd\tx4, x4, w2, uxtw #3\n"
                             "00000010:f85f8485 \tldr\tx5, [x4], #-8\n"
                             "00000014:f81f8fe5 \tstr\tx5, [sp, #-8]!\n"
                             "00000018:51000442 \tsub\tw2, w2, #1\n"
                             "0000001c:54ffffa5 \tb.pl\t0x10\n"
                             "00000020:d2800140 \tmov\tx0, #10\n");
    AssemblerAarch64 masm(chunk_);
    Label label1;
    Label labelLoop;
    Register count = w2;
    Register base = x4;
    Register temp = x5;
    __ Cmp(count, Immediate(0));
    __ B(Condition::EQ, &label1);
    __ Add(count, count, Immediate(-1));
    __ Add(base, base, Operand(count, UXTW, 3));
    __ Bind(&labelLoop);
    {
        __ Ldr(temp, MemoryOperand(base, -8, POSTINDEX));
        __ Str(temp, MemoryOperand(sp, -8, PREINDEX));
        __ Add(count, count, Immediate(-1));
        __ B(Condition::PL, &labelLoop);
    }
    __ Bind(&label1);
    {
        __ Mov(x0, Immediate(0xa));
    }
    std::ostringstream oss;
    DisassembleChunk(TARGET_AARCH64, &masm, oss);
    ASSERT_EQ(oss.str(), expectResult);
}

HWTEST_F_L0(AssemblerAarch64Test, TbzAndCbz)
{
    std::string expectResult("00000000:36780001 \ttbz\tw1, #15, 0x0\n"
                             "00000004:b60000c2 \ttbz\tx2, #32, 0x1c\n"
                             "00000008:372800c2 \ttbnz\tw2, #5, 0x20\n"
                             "0000000c:34000063 \tcbz\tw3, 0x18\n"
                             "00000010:b5000064 \tcbnz\tx4, 0x1c\n"
                             "00000014:b4000065 \tcbz\tx5, 0x20\n"
                             "00000018:b24003e0 \torr\tx0, xzr, #0x1\n"
                             "0000001c:b27f03e0 \torr\tx0, xzr, #0x2\n"
                             "00000020:b24007e0 \torr\tx0, xzr, #0x3\n");
    AssemblerAarch64 masm(chunk_);
    Label label1;
    Label label2;
    Label label3;
    __ Tbz(x1, 15, &label1);
    __ Tbz(x2, 32,  &label2);
    __ Tbnz(x2, 5,  &label3);
    __ Cbz(w3, &label1);
    __ Cbnz(x4, &label2);
    __ Cbz(x5, &label3);
    __ Bind(&label1);
    {
        __ Mov(x0, Immediate(0x1));
    }
    __ Bind(&label2);
    {
        __ Mov(x0, Immediate(0x2));
    }
    __ Bind(&label3);
    {
        __ Mov(x0, Immediate(0x3));
    }
    std::ostringstream oss;
    DisassembleChunk(TARGET_AARCH64, &masm, oss);
    ASSERT_EQ(oss.str(), expectResult);
}

HWTEST_F_L0(AssemblerAarch64Test, Call)
{
    std::string expectResult("00000000:b24003e0 \torr\tx0, xzr, #0x1\n"
                             "00000004:b27f03e1 \torr\tx1, xzr, #0x2\n"
                             "00000008:b24007e2 \torr\tx2, xzr, #0x3\n"
                             "0000000c:97fffffd \tbl\t0x0\n"
                             "00000010:d63f0040 \tblr\tx2\n");
    AssemblerAarch64 masm(chunk_);
    Label label1;
    __ Bind(&label1);
    {
        __ Mov(x0, Immediate(0x1));
        __ Mov(x1, Immediate(0x2));
        __ Mov(x2, Immediate(0x3));
        __ Bl(&label1);
        __ Blr(x2);
    }
    std::ostringstream oss;
    DisassembleChunk(TARGET_AARCH64, &masm, oss);
    ASSERT_EQ(oss.str(), expectResult);
}

HWTEST_F_L0(AssemblerAarch64Test, RetAndBrk)
{
    std::string expectResult("00000000:d65f03c0 \tret\n"
                             "00000004:d65f0280 \tret\tx20\n"
                             "00000008:d4200000 \tbrk\t#0\n");
    AssemblerAarch64 masm(chunk_);
    __ Ret();
    __ Ret(x20);
    __ Brk(Immediate(0));

    std::ostringstream oss;
    DisassembleChunk(TARGET_AARCH64, &masm, oss);
    ASSERT_EQ(oss.str(), expectResult);
}
#undef __
}