/*
 * Copyright (C) 2019 Intel Corporation. All rights reserved.
 * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 */

#include "gtest/gtest.h"
#include "bh_platform.h"
#include "wasm_runtime_common.h"
#include "aot_runtime.h"
#include "test_helper.h"

#ifndef __aligned
#define __aligned(n)
#endif
#include "wasm-apps/test_aot.h"

typedef struct MyAOTFrame {
    uintptr_t func_index;

    /* Instruction pointer: offset to the bytecode array */
    uintptr_t ip_offset;

    /* Operand stack top pointer of the current frame */
    uint32 *sp;

#if WASM_ENABLE_GC != 0
    /* Frame ref flags (GC only) */
    uint8 *frame_ref;
#endif

    uint32 lp[1];
} MyAOTFrame;

class AOTStackFrameTest : public testing::Test
{
  protected:
    virtual void SetUp()
    {
        memset(&init_args, 0, sizeof(RuntimeInitArgs));

        init_args.mem_alloc_type = Alloc_With_Pool;
        init_args.mem_alloc_option.pool.heap_buf = global_heap_buf;
        init_args.mem_alloc_option.pool.heap_size = sizeof(global_heap_buf);

        ASSERT_EQ(wasm_runtime_full_init(&init_args), true);
    }

    virtual void TearDown()
    {
        DestroyFrames();
        wasm_runtime_destroy();
    }

  public:
    static void DestroyFrames()
    {
        if (my_frames) {
            for (uint32 i = 0; i < my_frame_num; i++) {
                if (my_frames[i])
                    wasm_runtime_free(my_frames[i]);
            }
            wasm_runtime_free(my_frames);
            my_frames = NULL;
            my_frame_num = 0;
        }
    }

  public:
    RuntimeInitArgs init_args;
    wasm_module_t module = NULL;
    wasm_module_inst_t module_inst = NULL;
    wasm_function_inst_t func_inst = NULL;
    wasm_exec_env_t exec_env = NULL;
    static MyAOTFrame **my_frames;
    static uint32 my_frame_num;
    char error_buf[128];
    char global_heap_buf[512 * 1024];
    unsigned char test_aot_buf[16 * 1024];
    unsigned argv[8];
};

MyAOTFrame **AOTStackFrameTest::my_frames = NULL;
uint32 AOTStackFrameTest::my_frame_num = 0;

extern "C" {

typedef void (*stack_frame_callback_t)(struct WASMExecEnv *exec_env);

void
aot_set_stack_frame_callback(stack_frame_callback_t callback);

void
aot_stack_frame_cb(struct WASMExecEnv *exec_env)
{
    AOTModuleInstance *module_inst = (AOTModuleInstance *)exec_env->module_inst;
    AOTModule *module = (AOTModule *)module_inst->module;
    AOTFrame *frame = (AOTFrame *)exec_env->cur_frame;
    MyAOTFrame *my_frame, **my_frames;
    uint32 all_cell_num, max_local_cell_num, max_stack_cell_num;
    uint32 frame_size_old, frame_size, i, frame_num = 0, aot_func_idx;

    AOTStackFrameTest::DestroyFrames();

    while (frame) {
        frame_num++;
        frame = frame->prev_frame;
    }

    my_frames =
        (MyAOTFrame **)wasm_runtime_malloc(sizeof(MyAOTFrame *) * frame_num);
    bh_assert(my_frames);

    frame = (AOTFrame *)exec_env->cur_frame;
    for (i = 0; i < frame_num; i++) {
        aot_func_idx = frame->func_index;
        max_local_cell_num = module->max_local_cell_nums[aot_func_idx];
        max_stack_cell_num = module->max_stack_cell_nums[aot_func_idx];
        all_cell_num = max_local_cell_num + max_stack_cell_num;

        frame_size_old = (uint32)offsetof(AOTFrame, lp) + all_cell_num * 4;
        frame_size = (uint32)offsetof(MyAOTFrame, lp) + all_cell_num * 4;

        my_frames[frame_num - 1 - i] = my_frame =
            (MyAOTFrame *)wasm_runtime_malloc(frame_size);

        my_frame->func_index = aot_func_idx;
        my_frame->ip_offset = frame->ip_offset;
        my_frame->sp = my_frame->lp + (frame->sp - frame->lp);
#if WASM_ENABLE_GC != 0
        my_frame->frame_ref =
            (uint8 *)my_frame->lp + (frame->frame_ref - (uint8 *)frame->lp);
#endif

        bh_memcpy_s(my_frame->lp, all_cell_num * 4, frame->lp,
                    all_cell_num * 4);

        frame = frame->prev_frame;
    }

    AOTStackFrameTest::my_frames = my_frames;
    AOTStackFrameTest::my_frame_num = frame_num;
}
}

TEST_F(AOTStackFrameTest, test1)
{
    MyAOTFrame *frame, **frames;
    uint32 frame_num;

    aot_set_stack_frame_callback(aot_stack_frame_cb);

    bh_memcpy_s(test_aot_buf, sizeof(test_aot_buf), test_aot, sizeof(test_aot));

    module = wasm_runtime_load(test_aot_buf, sizeof(test_aot), error_buf,
                               sizeof(error_buf));
    ASSERT_TRUE(module != NULL);

    module_inst = wasm_runtime_instantiate(module, 16384, 0, error_buf,
                                           sizeof(error_buf));
    ASSERT_TRUE(module_inst != NULL);

    exec_env = wasm_runtime_create_exec_env(module_inst, 8 * 1024);
    ASSERT_TRUE(exec_env != NULL);

    func_inst = wasm_runtime_lookup_function(module_inst, "test1");
    ASSERT_TRUE(func_inst != NULL);

    argv[0] = 33;
    argv[1] = 44;
    wasm_runtime_call_wasm(exec_env, func_inst, 2, argv);
    ASSERT_TRUE(wasm_runtime_get_exception(module_inst));

    frames = AOTStackFrameTest::my_frames;
    frame_num = AOTStackFrameTest::my_frame_num;

    ASSERT_TRUE(frames != NULL);
    ASSERT_TRUE(frame_num == 1);

    ASSERT_TRUE(frames[0]->lp[0] == 33);
    ASSERT_TRUE(frames[0]->lp[1] == 44);
    ASSERT_TRUE(frames[0]->lp[2] == 0x11223344);
    ASSERT_TRUE(*(uint64 *)(frames[0]->lp + 3) == 0x12345678ABCDEF99LL);
    ASSERT_TRUE(*(float *)(frames[0]->lp + 5) == 5566.7788f);
    ASSERT_TRUE(*(double *)(frames[0]->lp + 6) == 99887766.55443322);

    wasm_runtime_destroy_exec_env(exec_env);
    exec_env = NULL;

    wasm_runtime_deinstantiate(module_inst);
    module_inst = NULL;

    wasm_runtime_unload(module);
    module = NULL;
}

TEST_F(AOTStackFrameTest, test2)
{
    MyAOTFrame *frame, **frames;
    uint32 frame_num;

    aot_set_stack_frame_callback(aot_stack_frame_cb);

    bh_memcpy_s(test_aot_buf, sizeof(test_aot_buf), test_aot, sizeof(test_aot));

    module = wasm_runtime_load(test_aot_buf, sizeof(test_aot), error_buf,
                               sizeof(error_buf));
    ASSERT_TRUE(module != NULL);

    module_inst = wasm_runtime_instantiate(module, 16384, 0, error_buf,
                                           sizeof(error_buf));
    ASSERT_TRUE(module_inst != NULL);

    exec_env = wasm_runtime_create_exec_env(module_inst, 8 * 1024);
    ASSERT_TRUE(exec_env != NULL);

    func_inst = wasm_runtime_lookup_function(module_inst, "test2");
    ASSERT_TRUE(func_inst != NULL);

    argv[0] = 1234;
    argv[1] = 5678;
    wasm_runtime_call_wasm(exec_env, func_inst, 2, argv);
    ASSERT_TRUE(wasm_runtime_get_exception(module_inst));

    frames = AOTStackFrameTest::my_frames;
    frame_num = AOTStackFrameTest::my_frame_num;

    ASSERT_TRUE(frames != NULL);
    ASSERT_TRUE(frame_num == 1);

    ASSERT_TRUE(frames[0]->lp[0] == 1234);
    ASSERT_TRUE(frames[0]->lp[1] == 5678);
    ASSERT_TRUE(frames[0]->lp[2] == 0x11223344);
    ASSERT_TRUE(*(uint64 *)(frames[0]->lp + 3) == 0x12345678ABCDEF99LL);
    ASSERT_TRUE(*(float *)(frames[0]->lp + 5) == 5566.7788f);
    ASSERT_TRUE(*(double *)(frames[0]->lp + 6) == 99887766.55443322);
    ASSERT_TRUE(frames[0]->lp[8] == 0x1234);
    ASSERT_TRUE(frames[0]->lp[9] == 0x5678);
}

TEST_F(AOTStackFrameTest, test3)
{
    MyAOTFrame *frame, **frames;
    uint32 frame_num;

    aot_set_stack_frame_callback(aot_stack_frame_cb);

    bh_memcpy_s(test_aot_buf, sizeof(test_aot_buf), test_aot, sizeof(test_aot));

    module = wasm_runtime_load(test_aot_buf, sizeof(test_aot), error_buf,
                               sizeof(error_buf));
    ASSERT_TRUE(module != NULL);

    module_inst = wasm_runtime_instantiate(module, 16384, 0, error_buf,
                                           sizeof(error_buf));
    ASSERT_TRUE(module_inst != NULL);

    exec_env = wasm_runtime_create_exec_env(module_inst, 8 * 1024);
    ASSERT_TRUE(exec_env != NULL);

    func_inst = wasm_runtime_lookup_function(module_inst, "test3");
    ASSERT_TRUE(func_inst != NULL);

    argv[0] = 1234;
    argv[1] = 5678;
    wasm_runtime_call_wasm(exec_env, func_inst, 2, argv);
    ASSERT_TRUE(wasm_runtime_get_exception(module_inst));

    frames = AOTStackFrameTest::my_frames;
    frame_num = AOTStackFrameTest::my_frame_num;

    ASSERT_TRUE(frames != NULL);
    ASSERT_TRUE(frame_num == 2);

    ASSERT_TRUE(frames[0]->sp - frames[0]->lp == 5);
    ASSERT_TRUE(frames[0]->ip_offset == 24);

    ASSERT_TRUE(frames[0]->lp[0] == 1234);
    ASSERT_TRUE(frames[0]->lp[1] == 5678);
    ASSERT_TRUE(frames[0]->lp[2] == 0x11223344);
    ASSERT_TRUE(*(uint64 *)(frames[0]->lp + 3) == 0x12345678ABCDEF99LL);

    ASSERT_TRUE(frames[1]->lp[0] == 0x1234);
    ASSERT_TRUE(frames[1]->lp[1] == 0x5678);
    ASSERT_TRUE(frames[1]->lp[2] == 0x11223344);
    ASSERT_TRUE(*(uint64 *)(frames[1]->lp + 3) == 0x12345678ABCDEF99LL);
    ASSERT_TRUE(*(float *)(frames[1]->lp + 5) == 5566.7788f);
    ASSERT_TRUE(*(double *)(frames[1]->lp + 6) == 99887766.55443322);
}
