jit.h 5.0 KiB raw
1
/*
2
 * JIT compiler for RISC-V RV64I -> x86-64.
3
 *
4
 * Translates basic blocks of RISC-V instructions into native x86-64 machine
5
 * code.  The generated code operates on the guest CPU state structure directly
6
 * and falls back to the interpreter for ecalls, ebreak, and faults.
7
 *
8
 * Design follows QEMU's TCG approach: translate on first encounter, cache the
9
 * result, and execute the native code on subsequent visits.
10
 */
11
#ifndef JIT_H
12
#define JIT_H
13
14
#include "riscv.h"
15
#include "types.h"
16
#include <stdbool.h>
17
18
/* Forward declarations. */
19
struct cpu;
20
21
/* ---------------------------------------------------------------------------
22
 * Tuning constants
23
 * ------------------------------------------------------------------------- */
24
25
/* Size of the JIT code cache (16 MB). */
26
#define JIT_CODE_CACHE_SIZE (16 * 1024 * 1024)
27
28
/* Maximum number of x86-64 bytes emitted per guest instruction.
29
 * Conservative upper bound -- most instructions compile to <30 bytes. */
30
#define JIT_MAX_INSN_BYTES 128
31
32
/* Maximum guest instructions in a single translated block. */
33
#define JIT_MAX_BLOCK_INSNS 256
34
35
/* Number of hash-table buckets for block lookup (must be power of 2). */
36
#define JIT_BLOCK_HASH_SIZE (64 * 1024)
37
38
/* Maximum number of translated blocks. */
39
#define JIT_MAX_BLOCKS (256 * 1024)
40
41
/* Exit reasons returned by generated code. */
42
enum jit_exit {
43
    JIT_EXIT_BRANCH = 0, /* End of block -- branch/jump, update PC. */
44
    JIT_EXIT_ECALL  = 1, /* ECALL -- needs interpreter handling. */
45
    JIT_EXIT_EBREAK = 2, /* EBREAK -- needs interpreter handling. */
46
    JIT_EXIT_FAULT  = 3, /* Memory fault -- needs interpreter handling. */
47
    JIT_EXIT_CHAIN  = 4, /* Chain to next block (PC already set). */
48
    JIT_EXIT_RET    = 5, /* Program returned (jalr x0, ra, 0 with ra==0). */
49
};
50
51
/* A translated block of native code. */
52
struct jit_block {
53
    u32               guest_pc;     /* Guest PC this block starts at. */
54
    u32               guest_end_pc; /* Guest PC *after* last instruction. */
55
    u32               insn_count; /* Number of guest instructions translated.*/
56
    u8               *code;       /* Pointer into code cache. */
57
    u32               code_size;  /* Size of generated x86-64 code. */
58
    struct jit_block *hash_next;  /* Hash chain. */
59
};
60
61
/* Complete JIT state. */
62
struct jit_state {
63
    /* Executable code cache (mmap'd RWX). */
64
    u8 *code_cache;
65
    u32 code_cache_used;
66
67
    /* Block hash table (open-chaining). */
68
    struct jit_block *block_hash[JIT_BLOCK_HASH_SIZE];
69
70
    /* Block storage (static pool). */
71
    struct jit_block blocks[JIT_MAX_BLOCKS];
72
    u32              block_count;
73
74
    /* Statistics. */
75
    u64 blocks_compiled;
76
    u64 blocks_executed;
77
    u64 insns_executed;
78
79
    /* Whether JIT is available (mmap succeeded, x86-64 host). */
80
    bool available;
81
};
82
83
/* ---------------------------------------------------------------------------
84
 * API
85
 * ------------------------------------------------------------------------- */
86
87
/* Initialise the JIT.  Returns true on success.
88
 * On non-x86-64 hosts or if mmap fails, returns false and the emulator
89
 * should fall back to the interpreter. */
90
bool jit_init(struct jit_state *jit);
91
92
/* Release JIT resources. */
93
void jit_destroy(struct jit_state *jit);
94
95
/* Flush the entire translation cache (e.g. after self-modifying code). */
96
void jit_flush(struct jit_state *jit);
97
98
/* Compile (but don't look up) a block starting at `guest_pc`.
99
 * Returns NULL if the code cache is full or compilation fails. */
100
struct jit_block *jit_compile_block(
101
    struct jit_state *jit,
102
    u32               guest_pc,
103
    u8               *memory,
104
    u32               program_base,
105
    u32               program_bytes
106
);
107
108
/* Inline fast-path block lookup (hash table probe). */
109
static inline struct jit_block *jit_lookup_block(
110
    struct jit_state *jit, u32 guest_pc
111
) {
112
    u32               h = (guest_pc >> 2) & (JIT_BLOCK_HASH_SIZE - 1);
113
    struct jit_block *b = jit->block_hash[h];
114
    while (b) {
115
        if (b->guest_pc == guest_pc)
116
            return b;
117
        b = b->hash_next;
118
    }
119
    return NULL;
120
}
121
122
/* Look up or compile a block.  Calls the inline lookup first, falls
123
 * back to the compiler on a miss. */
124
static inline struct jit_block *jit_get_block(
125
    struct jit_state *jit,
126
    u32               guest_pc,
127
    u8               *memory,
128
    u32               program_base,
129
    u32               program_bytes
130
) {
131
    struct jit_block *b = jit_lookup_block(jit, guest_pc);
132
    if (b)
133
        return b;
134
    return jit_compile_block(
135
        jit, guest_pc, memory, program_base, program_bytes
136
    );
137
}
138
139
/* Signature of generated block entry points.
140
 * Args: pointer to cpu->regs[], pointer to memory[].
141
 * Returns: exit reason (enum jit_exit). */
142
typedef int (*jit_block_fn)(u64 *regs, u8 *memory, u32 *pc_out);
143
144
/* Execute a translated block.  Returns the exit reason. */
145
static inline int jit_exec_block(
146
    struct jit_block *block, u64 *regs, u8 *memory, u32 *pc_out
147
) {
148
    jit_block_fn fn = (jit_block_fn)(void *)block->code;
149
    return fn(regs, memory, pc_out);
150
}
151
152
#endif /* JIT_H */