#ifndef __DYNAREC_PPC64LE_PRIVATE_H_
#define __DYNAREC_PPC64LE_PRIVATE_H_

#include <stdint.h>

#include "../dynarec_private.h"

typedef struct x64emu_s x64emu_t;
typedef struct dynablock_s dynablock_t;
typedef struct instsize_s instsize_t;
typedef struct box64env_s box64env_t;

#define BARRIER_MAYBE   8

// VMX cache types (mirrors ARM64's NEON cache / LA64's LSX cache)
// PPC64LE uses VMX (128-bit) registers vr0-vr31 (= vs32-vs63) for SIMD caching
#define VMX_CACHE_NONE      0
#define VMX_CACHE_ST_D      1
#define VMX_CACHE_ST_F      2
#define VMX_CACHE_ST_I64    3
#define VMX_CACHE_MM        4
#define VMX_CACHE_XMMW      5
#define VMX_CACHE_XMMR      6
#define VMX_CACHE_YMMW      7
#define VMX_CACHE_YMMR      8
#define VMX_CACHE_SCR       9

typedef union vmx_cache_s {
    int8_t           v;
    struct {
        uint8_t t:4;   // reg type
        uint8_t n:4;   // reg number
    };
} vmx_cache_t;

typedef union sse_cache_s {
    int8_t      v;
    struct {
        uint8_t reg:7;
        uint8_t write:1;
    };
} sse_cache_t;

typedef union avx_cache_s {
    int8_t v;
    struct {
        uint8_t reg : 5;
        uint8_t width : 1;
        uint8_t zero_upper : 1;
        uint8_t write : 1;
    };
} avx_cache_t;

typedef struct vmxcache_s {
    // VMX cache
    vmx_cache_t         vmxcache[32];
    int8_t              stack;
    int8_t              stack_next;
    int8_t              stack_pop;
    int8_t              stack_push;
    uint8_t             combined1;
    uint8_t             combined2;
    uint8_t             swapped;        // the combined reg were swapped
    uint8_t             barrier;        // is there a barrier at instruction epilog?
    uint8_t             pushed;         // positive pushed value (to check for overflow)
    uint8_t             poped;          // positive poped value (to check for underflow)
    uint32_t            news;           // bitmask, which vmxcache are new for this opcode
    // fpu cache
    int8_t              x87cache[8];    // cache status for the 8 x87 register behind the fpu stack
    int8_t              x87reg[8];      // reg used for x87cache entry
    int16_t             tags;           // similar to fpu_tags
    int8_t              mmxcache[8];    // cache status for the 8 MMX registers
    sse_cache_t         ssecache[16];   // cache status for the 16 SSE(2) registers
    avx_cache_t         avxcache[16];   // cache status for the 16 AVX registers
    int8_t              fpuused[32];    // all vmx regs, used by x87, mmx, sse and avx
    int8_t              x87stack;       // cache stack counter
    int8_t              mmxcount;       // number of mmx register used (not both mmx and x87 at the same time)
    int8_t              fpu_scratch;    // scratch counter
    uint16_t            xmm_write;      // 1bit of xmmXX removed write
    uint16_t            xmm_removed;    // 1bit if xmmXX was removed
    uint16_t            xmm_used;       // mask of the xmm regs used in this opcode
    uint16_t            ymm_used;       // mask of the ymm regs used in this opcode
    uint16_t            ymm_write;      // 1bit of ymmXX removed write
    uint16_t            ymm_removed;    // 1bit if ymmXX was removed
    uint16_t            xmm_unneeded;   // 1bit for xmmXX were value is not needed
    uint16_t            ymm_unneeded;   // 1bit for ymmXX were value is not needed
    uint64_t            ymm_regs;       // 4bits (0-15) position of 16 ymmXX regs removed
} vmxcache_t;

typedef enum flagcache_s {
    status_unk = 0,         // unknown deferred flags status
    status_set,             // deferred flags set to something (not 0)
    status_none_pending,    // deferred flags set to 0, but still pending the write to x64emu_t
    status_none,            // deferred flags set to 0, written to x64emu_t
} flagcache_t;

typedef struct callret_s callret_t;
typedef struct sep_s sep_t;

typedef struct instruction_ppc64le_s {
    instruction_x64_t   x64;
    uintptr_t           address;    // (start) address of the ppc64le emitted instruction
    uintptr_t           epilog;     // epilog of current instruction (can be start of next, or barrier stuff)
    int                 size;       // size of the ppc64le emitted instruction
    int                 size2;      // size of the ppc64le emitted instruction after pass2
    int                 pred_sz;    // size of predecessor list
    int                 *pred;      // predecessor array
    uintptr_t           mark[3];
    uintptr_t           markf[2];
    uintptr_t           markseg;
    uintptr_t           marklock;
    uintptr_t           marklock2;
    int                 pass2choice;// value for choices that are fixed on pass2 for pass3
    uintptr_t           natcall;
    uint16_t            retn;
    uint16_t            purge_ymm;  // need to purge some ymm
    uint16_t            ymm0_in;    // bitmap of ymm to zero at purge
    uint16_t            ymm0_add;   // the ymm0 added by the opcode
    uint16_t            ymm0_sub;   // the ymm0 removed by the opcode
    uint16_t            ymm0_out;   // the ymm0 at the end of the opcode
    uint16_t            ymm0_pass2, ymm0_pass3;
    uint8_t             barrier_maybe;
    uint8_t             will_write:2;    // [strongmem] will write to memory
    uint8_t             will_read:1;     // [strongmem] will read from memory
    uint8_t             last_write:1;    // [strongmem] the last write in a SEQ
    uint8_t             lock:1;          // [strongmem] lock semantic
    uint8_t             df_needed:1;
    uint8_t             df_notneeded:1;
    uint8_t             nat_flags_fusion:1;
    uint8_t             nat_flags_nofusion:1;
    uint8_t             nat_flags_carry:1;
    uint8_t             nat_flags_sign:1;
    uint8_t             nat_flags_sf:1;
    uint8_t             nat_flags_needsign:1;
    uint8_t             no_scratch_usage:1;  // this opcode does not use scratch register
    uint8_t             nat_flags_op1;
    uint8_t             nat_flags_op2;
    uint8_t             unaligned:1;    // this opcode can be re-generated for unaligned special case
    uint8_t             x87precision:1; // this opcode can handle x87pc
    unsigned            mmx_used:1; // no fine tracking, just a global "any reg used"
    unsigned            x87_used:1; // no fine tracking, just a global "any reg used"
    unsigned            fpu_used:1; // any xmm/ymm/x87/mmx reg used
    unsigned            fpupurge:1;   // this opcode will purge all fpu regs
    uint16_t            nat_next_inst;
    flagcache_t         f_exit;     // flags status at end of instruction
    vmxcache_t          v;          // vmxcache at end of instruction (but before poping)
    flagcache_t         f_entry;    // flags status before the instruction begin
    int                 cacheupd;
} instruction_ppc64le_t;

typedef struct dynarec_ppc64le_s {
    instruction_ppc64le_t* insts;
    int32_t             size;
    int32_t             cap;
    uintptr_t           start;      // start of the block
    uintptr_t           end;        // maximum end of the block (only used in pass0)
    uint32_t            isize;      // size in bytes of x64 instructions included
    uint32_t            prefixsize; // size in byte of the prefix of the block
    void*               block;      // memory pointer where next instruction is emitted
    uintptr_t           native_start;  // start of the ppc64le code
    size_t              native_size;   // size of emitted ppc64le code
    uintptr_t           last_ip;    // last set IP in RIP (or NULL if unclean state)
    uint64_t*           table64;    // table of 64bits values
    int                 table64size;// size of table (will be appended at end of executable code)
    int                 table64cap;
    uintptr_t           tablestart;
    uintptr_t           jmp_next;   // address of the jump_next address
    flagcache_t         f;
    vmxcache_t          v;          // cache for VMX regs from fpu, plus x87 stack delta
    uintptr_t*          next;       // variable array of "next" jump address
    int                 next_sz;
    int                 next_cap;
    int*                jmps;       // variable array of jump instructions
    int                 jmp_sz;
    int                 jmp_cap;
    int*                predecessor;// single array of all predecessor
    dynablock_t*        dynablock;
    instsize_t*         instsize;
    size_t              insts_size; // size of the instruction size array (calculated)
    int                 callret_size;   // size of the array
    int                 sep_size;   // size of the array
    callret_t*          callrets;   // array of callret return, with NOP / UDF depending if the block is clean or dirty
    sep_t*              sep;        // array of secondary entry point
    uintptr_t           forward;    // address of the last end of code while testing forward
    uintptr_t           forward_to; // address of the next jump to (to check if everything is ok)
    int32_t             forward_size;   // size at the forward point
    int                 forward_ninst;  // ninst at the forward point
    uint16_t            ymm_zero;   // bitmap of ymm to zero at purge
    uint8_t             smwrite;    // for strongmem model emulation
    uint8_t             doublepush;
    uint8_t             doublepop;
    uint8_t             always_test;
    uint8_t             abort;      // abort the creation of the block
    uint8_t             use_x87:1;  // set if x87 regs are used
    uint8_t             use_mmx:1;
    uint8_t             use_xmm:1;
    uint8_t             use_ymm:1;
    uint8_t             have_purge:1;   // set to 1 if block can be purged
    uint8_t             is_file_mapped:1;   // if the memory is a mapped file (probably binary, not a memory)
    void*               gdbjit_block;
    uint32_t            need_x87check;  // needs x87 precision control check if non-null, or 0 if not
    uint32_t            need_dump;     // need to dump the block
    int                 need_reloc; // does the dynablock need relocations
    int                 reloc_size;
    uint32_t*           relocs;
    box64env_t*         env;
} dynarec_ppc64le_t;

void add_next(dynarec_ppc64le_t *dyn, uintptr_t addr);
uintptr_t get_closest_next(dynarec_ppc64le_t *dyn, uintptr_t addr);
void add_jump(dynarec_ppc64le_t *dyn, int ninst);
int get_first_jump(dynarec_ppc64le_t *dyn, int next);
int get_first_jump_addr(dynarec_ppc64le_t *dyn, uintptr_t next);
int is_nops(dynarec_ppc64le_t *dyn, uintptr_t addr, int n);

int isTable64(dynarec_ppc64le_t *dyn, uint64_t val); // return 1 if val already in Table64
int Table64(dynarec_ppc64le_t *dyn, uint64_t val, int pass);  // add a value to table64 (if needed) and gives back the offset to use

void CreateJmpNext(void* addr, void* next);

// While we could theoretically traverse forward to find the flags-consuming x86
// instruction and get the exact scratch registers to save, this is too complicated.
// So we went with the simpler approach of saving all scratch registers, this won't
// add noticeable performance overhead in trace mode.
#define SPILL_NF_REGISTERS          \
    do {                            \
        ADDI(xSP, xSP, -64);        \
        STD(x1, 0 * 8, xSP);        \
        STD(x2, 1 * 8, xSP);        \
        STD(x3, 2 * 8, xSP);        \
        STD(x4, 3 * 8, xSP);        \
        STD(x5, 4 * 8, xSP);        \
        STD(x6, 5 * 8, xSP);        \
        STD(x7, 6 * 8, xSP);        \
    } while(0);

#define RESTORE_NF_REGISTERS        \
    do {                            \
        LD(x1, 0 * 8, xSP);         \
        LD(x2, 1 * 8, xSP);         \
        LD(x3, 2 * 8, xSP);         \
        LD(x4, 3 * 8, xSP);         \
        LD(x5, 4 * 8, xSP);         \
        LD(x6, 5 * 8, xSP);         \
        LD(x7, 6 * 8, xSP);         \
        ADDI(xSP, xSP, 64);         \
    } while(0);

#define GO_TRACE(A, B, s0)          \
    GETIP(addr, s0);                \
    MV(x1, xRIP);                   \
    STORE_XEMU_CALL();              \
    MOV64x(x2, B);                  \
    CALL(const_##A, -1, x1, x2);   \
    LOAD_XEMU_CALL()

#endif //__DYNAREC_PPC64LE_PRIVATE_H_
