mirror of
https://github.com/ish-app/ish.git
synced 2026-01-25 14:06:40 +00:00
263 lines
8.2 KiB
C
263 lines
8.2 KiB
C
#define DEFAULT_CHANNEL instr
|
|
#include "debug.h"
|
|
#include "jit/jit.h"
|
|
#include "jit/gen.h"
|
|
#include "jit/frame.h"
|
|
#include "emu/cpu.h"
|
|
#include "emu/memory.h"
|
|
#include "emu/interrupt.h"
|
|
#include "util/list.h"
|
|
#include "kernel/calls.h"
|
|
|
|
static void jit_block_free(struct jit *jit, struct jit_block *block);
|
|
static void jit_resize_hash(struct jit *jit, size_t new_size);
|
|
|
|
struct jit *jit_new(struct mem *mem) {
|
|
struct jit *jit = calloc(1, sizeof(struct jit));
|
|
jit->mem = mem;
|
|
jit_resize_hash(jit, JIT_INITIAL_HASH_SIZE);
|
|
lock_init(&jit->lock);
|
|
return jit;
|
|
}
|
|
|
|
void jit_free(struct jit *jit) {
|
|
for (size_t i = 0; i < jit->hash_size; i++) {
|
|
struct jit_block *block, *tmp;
|
|
if (list_null(&jit->hash[i]))
|
|
continue;
|
|
list_for_each_entry_safe(&jit->hash[i], block, tmp, chain) {
|
|
jit_block_free(jit, block);
|
|
}
|
|
}
|
|
free(jit->hash);
|
|
free(jit);
|
|
}
|
|
|
|
static inline struct list *blocks_list(struct jit *jit, page_t page, int i) {
|
|
return &mem_pt(jit->mem, page)->blocks[i];
|
|
}
|
|
|
|
void jit_invalidate_page(struct jit *jit, page_t page) {
|
|
lock(&jit->lock);
|
|
struct jit_block *block, *tmp;
|
|
for (int i = 0; i <= 1; i++) {
|
|
struct list *blocks = blocks_list(jit, page, i);
|
|
if (list_null(blocks))
|
|
continue;
|
|
list_for_each_entry_safe(blocks, block, tmp, page[i]) {
|
|
jit_block_free(jit, block);
|
|
}
|
|
}
|
|
unlock(&jit->lock);
|
|
}
|
|
|
|
static void jit_resize_hash(struct jit *jit, size_t new_size) {
|
|
TRACE("%d resizing hash to %lu, using %lu bytes for gadgets\n", current->pid, new_size, jit->mem_used);
|
|
struct list *new_hash = calloc(new_size, sizeof(struct list));
|
|
for (size_t i = 0; i < jit->hash_size; i++) {
|
|
if (list_null(&jit->hash[i]))
|
|
continue;
|
|
struct jit_block *block, *tmp;
|
|
list_for_each_entry_safe(&jit->hash[i], block, tmp, chain) {
|
|
list_remove(&block->chain);
|
|
list_init_add(&new_hash[block->addr % new_size], &block->chain);
|
|
}
|
|
}
|
|
free(jit->hash);
|
|
jit->hash = new_hash;
|
|
jit->hash_size = new_size;
|
|
}
|
|
|
|
static void jit_insert(struct jit *jit, struct jit_block *block) {
|
|
jit->mem_used += block->used;
|
|
jit->num_blocks++;
|
|
// target an average hash chain length of 1-2
|
|
if (jit->num_blocks >= jit->hash_size * 2)
|
|
jit_resize_hash(jit, jit->hash_size * 2);
|
|
|
|
list_init_add(&jit->hash[block->addr % jit->hash_size], &block->chain);
|
|
if (mem_pt(jit->mem, PAGE(block->addr)) == NULL)
|
|
return;
|
|
list_init_add(blocks_list(jit, PAGE(block->addr), 0), &block->page[0]);
|
|
if (PAGE(block->addr) != PAGE(block->end_addr))
|
|
list_init_add(blocks_list(jit, PAGE(block->end_addr), 1), &block->page[1]);
|
|
}
|
|
|
|
static struct jit_block *jit_lookup(struct jit *jit, addr_t addr) {
|
|
struct list *bucket = &jit->hash[addr % jit->hash_size];
|
|
if (list_null(bucket))
|
|
return NULL;
|
|
struct jit_block *block;
|
|
list_for_each_entry(bucket, block, chain) {
|
|
if (block->addr == addr)
|
|
return block;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static struct jit_block *jit_block_compile(addr_t ip, struct tlb *tlb) {
|
|
struct gen_state state;
|
|
TRACE("%d %08x --- compiling:\n", current->pid, ip);
|
|
gen_start(ip, &state);
|
|
while (true) {
|
|
if (!gen_step32(&state, tlb))
|
|
break;
|
|
// no block should span more than 2 pages
|
|
// guarantee this by limiting total block size to 1 page
|
|
// guarantee that by stopping as soon as there's less space left than
|
|
// the maximum length of an x86 instruction
|
|
// TODO refuse to decode instructions longer than 15 bytes
|
|
if (state.ip - ip >= PAGE_SIZE - 15) {
|
|
gen_exit(&state);
|
|
break;
|
|
}
|
|
}
|
|
gen_end(&state);
|
|
assert(state.ip - ip <= PAGE_SIZE);
|
|
state.block->used = state.capacity;
|
|
return state.block;
|
|
}
|
|
|
|
static void jit_block_free(struct jit *jit, struct jit_block *block) {
|
|
if (jit != NULL) {
|
|
jit->mem_used -= block->used;
|
|
jit->num_blocks--;
|
|
}
|
|
list_remove(&block->chain);
|
|
for (int i = 0; i <= 1; i++) {
|
|
list_remove(&block->page[i]);
|
|
list_remove_safe(&block->jumps_from_links[i]);
|
|
|
|
struct jit_block *last_block, *tmp;
|
|
list_for_each_entry_safe(&block->jumps_from[i], last_block, tmp, jumps_from_links[i]) {
|
|
if (last_block->jump_ip[i] != NULL)
|
|
*last_block->jump_ip[i] = last_block->old_jump_ip[i];
|
|
list_remove(&last_block->jumps_from_links[i]);
|
|
}
|
|
}
|
|
free(block);
|
|
}
|
|
|
|
int jit_enter(struct jit_block *block, struct jit_frame *frame, struct tlb *tlb);
|
|
|
|
#if 1
|
|
|
|
static inline size_t jit_cache_hash(addr_t ip) {
|
|
return (ip ^ (ip >> 12)) % JIT_CACHE_SIZE;
|
|
}
|
|
|
|
void cpu_run(struct cpu_state *cpu) {
|
|
struct tlb tlb;
|
|
tlb_init(&tlb, cpu->mem);
|
|
struct jit *jit = cpu->mem->jit;
|
|
struct jit_block *cache[JIT_CACHE_SIZE] = {};
|
|
struct jit_frame frame = {.cpu = *cpu};
|
|
|
|
int i = 0;
|
|
read_wrlock(&cpu->mem->lock);
|
|
unsigned changes = cpu->mem->changes;
|
|
|
|
while (true) {
|
|
addr_t ip = frame.cpu.eip;
|
|
size_t cache_index = jit_cache_hash(ip);
|
|
struct jit_block *block = cache[cache_index];
|
|
if (block == NULL || block->addr != ip) {
|
|
lock(&jit->lock);
|
|
block = jit_lookup(jit, ip);
|
|
if (block == NULL) {
|
|
block = jit_block_compile(ip, &tlb);
|
|
jit_insert(jit, block);
|
|
} else {
|
|
TRACE("%d %08x --- missed cache\n", current->pid, ip);
|
|
}
|
|
cache[cache_index] = block;
|
|
unlock(&jit->lock);
|
|
}
|
|
struct jit_block *last_block = frame.last_block;
|
|
if (last_block != NULL &&
|
|
(last_block->jump_ip[0] != NULL ||
|
|
last_block->jump_ip[1] != NULL)) {
|
|
lock(&jit->lock);
|
|
for (int i = 0; i <= 1; i++) {
|
|
if (last_block->jump_ip[i] != NULL &&
|
|
(*last_block->jump_ip[i] & 0xffffffff) == block->addr) {
|
|
*last_block->jump_ip[i] = (unsigned long) block->code;
|
|
list_add(&block->jumps_from[i], &last_block->jumps_from_links[i]);
|
|
}
|
|
}
|
|
unlock(&jit->lock);
|
|
}
|
|
frame.last_block = block;
|
|
|
|
TRACE("%d %08x --- cycle %d\n", current->pid, ip, i);
|
|
int interrupt = jit_enter(block, &frame, &tlb);
|
|
if (interrupt == INT_NONE && ++i % (1 << 10) == 0)
|
|
interrupt = INT_TIMER;
|
|
if (interrupt != INT_NONE) {
|
|
*cpu = frame.cpu;
|
|
cpu->trapno = interrupt;
|
|
read_wrunlock(&cpu->mem->lock);
|
|
handle_interrupt(interrupt);
|
|
read_wrlock(&cpu->mem->lock);
|
|
|
|
jit = cpu->mem->jit;
|
|
last_block = NULL;
|
|
tlb.mem = cpu->mem;
|
|
if (cpu->mem->changes != changes) {
|
|
tlb_flush(&tlb);
|
|
changes = cpu->mem->changes;
|
|
}
|
|
memset(cache, 0, sizeof(cache));
|
|
frame.cpu = *cpu;
|
|
frame.last_block = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
#else
|
|
|
|
void cpu_run(struct cpu_state *cpu) {
|
|
int i = 0;
|
|
struct tlb tlb = {.mem = cpu->mem};
|
|
tlb_flush(&tlb);
|
|
read_wrlock(&cpu->mem->lock);
|
|
int changes = cpu->mem->changes;
|
|
while (true) {
|
|
int interrupt = cpu_step32(cpu, &tlb);
|
|
if (interrupt == INT_NONE && i++ >= 100000) {
|
|
i = 0;
|
|
interrupt = INT_TIMER;
|
|
}
|
|
if (interrupt != INT_NONE) {
|
|
cpu->trapno = interrupt;
|
|
read_wrunlock(&cpu->mem->lock);
|
|
handle_interrupt(interrupt);
|
|
read_wrlock(&cpu->mem->lock);
|
|
if (tlb.mem != cpu->mem)
|
|
tlb.mem = cpu->mem;
|
|
if (cpu->mem->changes != changes) {
|
|
tlb_flush(&tlb);
|
|
changes = cpu->mem->changes;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
// really only here for ptraceomatic
|
|
int cpu_step32(struct cpu_state *cpu, struct tlb *tlb) {
|
|
struct gen_state state;
|
|
gen_start(cpu->eip, &state);
|
|
gen_step32(&state, tlb);
|
|
gen_exit(&state);
|
|
gen_end(&state);
|
|
|
|
struct jit_block *block = state.block;
|
|
struct jit_frame frame = {.cpu = *cpu};
|
|
int interrupt = jit_enter(block, &frame, tlb);
|
|
*cpu = frame.cpu;
|
|
jit_block_free(NULL, block);
|
|
return interrupt;
|
|
}
|