#ifndef __DYNAREC_RV64_PRIVATE_H_
#define __DYNAREC_RV64_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;

#define BARRIER_MAYBE   8

#define EXT_CACHE_NONE   0
#define EXT_CACHE_ST_D   1
#define EXT_CACHE_ST_F   2
#define EXT_CACHE_ST_I64 3
#define EXT_CACHE_MM     4
#define EXT_CACHE_SS     5
#define EXT_CACHE_SD     6
#define EXT_CACHE_SCR    7
#define EXT_CACHE_XMMW   8
#define EXT_CACHE_XMMR   9
#define EXT_CACHE_YMMW   10
#define EXT_CACHE_YMMR   11
#define EXT_CACHE_MMV    12

#define EXT_CACHE_OLD_SD   0
#define EXT_CACHE_OLD_SS   1
#define EXT_CACHE_OLD_XMMW 2
#define EXT_CACHE_OLD_XMMR 3

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

typedef union mmx_cache_s {
    int8_t v;
    struct {
        uint8_t reg : 7;
        uint8_t vector : 1;
    };
} mmx_cache_t;

typedef union sse_cache_s {
    int16_t v;
    struct {
        uint16_t reg : 13;
        uint16_t vector : 1;
        uint16_t single : 1;
        uint16_t write : 1;
    };
} sse_cache_t;

typedef union sse_old_s {
    int8_t      v;
    struct {
        uint8_t     changed:1;
        uint8_t     purged:1;
        uint8_t     reg:4;
        uint8_t     type:2;
    };
} sse_old_t;

typedef struct extcache_s {
    // ext cache
    ext_cache_t         extcache[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, wich neoncache are new for this opcode
    sse_old_t           olds[16];       // SSE regs has changed or has been removed
    // 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
    mmx_cache_t         mmxcache[8];    // cache status for the 8 MMX registers
    sse_cache_t         ssecache[16];   // cache status for the 16 SSE(2) registers
    int8_t              fpuused[32];    // all double reg from fpu, 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
    int8_t              fpu_extra_qscratch; // some opcode need an extra quad scratch register
    int8_t              fpu_reg;        // x87/sse/mmx reg counter
} extcache_t;

typedef struct flagcache_s {
    int                 pending;    // is there a pending flags here, or to check?
    uint8_t             dfnone;     // if deferred flags is already set to df_none
    uint8_t             dfnone_here;// defered flags is cleared in this opcode
} flagcache_t;

typedef struct instruction_rv64_s {
    instruction_x64_t   x64;
    uintptr_t           address;    // (start) address of the riscv emitted instruction
    uintptr_t           epilog;     // epilog of current instruction (can be start of next, or barrier stuff)
    int                 size;       // size of the riscv emitted instruction
    int                 size2;      // size of the riscv 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 th end of the opcode
    uint16_t            ymm0_pass2, ymm0_pass3;
    int                 barrier_maybe;
    uint8_t             will_write;
    uint8_t             last_write;
    uint8_t             lock;
    uint8_t             df_notneeded;
    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_needsign:1;
    uint8_t             unaligned:1; // this opcode can be re-generated for unaligned special case
    uint8_t             nat_flags_op1;
    uint8_t             nat_flags_op2;
    flagcache_t         f_exit;     // flags status at end of instruction
    extcache_t          e;          // extcache at end of instruction (but before poping)
    flagcache_t         f_entry;    // flags status before the instruction begin
    uint8_t             vector_sew_entry; // sew status before the instruction begin
    uint8_t             vector_sew_exit;  // sew status at the end of instruction
} instruction_rv64_t;

typedef struct dynarec_rv64_s {
    instruction_rv64_t* insts;
    int32_t             size;
    int32_t             cap;
    uintptr_t           start;      // start of the block
    uint32_t            isize;      // size in byte of x64 instructions included
    void*               block;      // memory pointer where next instruction is emitted
    uintptr_t           native_start;  // start of the riscv code
    size_t              native_size;   // size of emitted riscv code
    uintptr_t           last_ip;    // last set IP in RIP (or NULL if unclean state) TODO: move to a cache something
    uint64_t*           table64;   // table of 64bits value
    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;
    extcache_t          e;          // cache for the 10..31 0..1 double reg 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)
    uint8_t             smwrite;    // for strongmem model emulation
    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             always_test;
    uint8_t             abort;
    uint8_t             vector_sew;     // current sew status
    uint8_t             vector_eew;     // current effective sew status, should only be used after SET_ELEMENT_WIDTH
    uint8_t             inst_sew;       // sew inside current instruction, for vsetvli elimination
    uint8_t             inst_vl;        // vl inside current instruction, for vsetvli elimination
    uint8_t             inst_vlmul;     // vlmul inside current instruction
    void*               gdbjit_block;
} dynarec_rv64_t;

// v0 is hardware wired to vector mask register, which should be always reserved
// convert idx (0..23) to reg index (10..31 1..2)
#define EXTREG(A) (((A) + 10 + ((A) > 21)) & 31)
// convert reg index (10..31 1..2) to idx (0..23)
#define EXTIDX(A) (((A) - 10 - ((A) < 3)) & 31)

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

int Table64(dynarec_rv64_t *dyn, uint64_t val, int pass);  // add a value to table64 (if needed) and gives back the imm19 to use in LDR_literal

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

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

#endif //__DYNAREC_RV64_PRIVATE_H_
