[subrepo]
remote = https://github.com/pcercuei/lightrec.git
branch = master
- commit = 6c69e104d0827e45b8c094d6a61f95c96e9efb15
- parent = b7ee664796db949b417754d11d4ae405cf5144a5
+ commit = 2081869a00371dac285836fb950f8cd0c26b55b9
+ parent = 5c00ea32a0eab812299b08acd14c25bf6ba4ca7a
method = merge
cmdver = 0.4.1
option(ENABLE_THREADED_COMPILER "Enable threaded compiler" ON)
if (ENABLE_THREADED_COMPILER)
- list(APPEND LIGHTREC_SOURCES recompiler.c)
+ list(APPEND LIGHTREC_SOURCES recompiler.c reaper.c)
if (NOT ENABLE_FIRST_PASS)
message(SEND_ERROR "Threaded compiler requires first-pass optimization")
+++ /dev/null
-LightRec is my attempt at creating a dynamic recompiler for MIPS and powered by GNU Lightning.
--- /dev/null
+
+# Lightrec
+
+Lightrec is a MIPS-to-everything dynamic recompiler for
+PlayStation emulators, using
+[GNU Lightning](https://www.gnu.org/software/lightning/)
+as the code emitter.
+
+As such, in theory it should be able to run on every CPU that Lightning
+can generate code for; including, but not limited to, __x86__, __x86_64__,
+__ARM__, __Aarch64__, __MIPS__, __PowerPC__ and __Risc-V__.
+
+## Features
+
+* __High-level optimizations__. The MIPS code is first pre-compiled into
+a form of Intermediate Representation (IR).
+Basically, just a single-linked list of structures representing the
+instructions. On that list, several optimization steps are performed:
+instructions are modified, reordered, tagged; new meta-instructions
+can be added, for instance to tell the code generator that a certain
+register won't be used anymore.
+
+* __Lazy compilation__.
+If Lightrec detects a block of code that would be very hard to
+compile properly (e.g. a branch with a branch in its delay slot),
+the block is marked as not compilable, and will always be emulated
+with the built-in interpreter. This allows to keep the code emitter
+simple and easy to understand.
+
+* __Run-time profiling__.
+The generated code will gather run-time information about the I/O access
+(whether they hit RAM, or hardware registers).
+The code generator will then use this information to generate direct
+read/writes to the emulated memories, instead of jumping to C for
+every call.
+
+* __Threaded compilation__.
+When entering a loading zone, where a lot of code has to be compiled,
+we don't want the compilation process to slow down the pace of emulation.
+To avoid that, the code compiler optionally runs on a thread, and the
+main loop will emulate the blocks that have not been compiled yet with
+the interpreter. This helps to drastically reduce the stutter that
+typically happens when a lot of new code is run.
+
+## Emulators
+
+Lightrec has been ported to the following emulators:
+
+* [__PCSX-ReArmed__ (my own fork)](https://github.com/pcercuei/pcsx_rearmed)
+
+* [__pcsx4all__ (my own fork)](https://github.com/pcercuei/pcsx4all)
+
+* [__Beetle__ (libretro)](https://github.com/libretro/beetle-psx-libretro/)
\ No newline at end of file
}
-void lightrec_mark_for_recompilation(struct blockcache *cache,
- struct block *block)
-{
- block->flags |= BLOCK_SHOULD_RECOMPILE;
-}
-
void lightrec_register_block(struct blockcache *cache, struct block *block)
{
u32 pc = kunseg(block->pc);
u32 pc = kunseg(block->pc);
struct block *old = cache->lut[(pc >> 2) & (LUT_SIZE - 1)];
- remove_from_code_lut(cache, block);
-
if (old == block) {
cache->lut[(pc >> 2) & (LUT_SIZE - 1)] = old->next;
return;
/*
- * Copyright (C) 2014 Paul Cercueil <paul@crapouillou.net>
+ * Copyright (C) 2014-2020 Paul Cercueil <paul@crapouillou.net>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
u32 lightrec_calculate_block_hash(const struct block *block);
_Bool lightrec_block_is_outdated(struct block *block);
-void lightrec_mark_for_recompilation(struct blockcache *cache,
- struct block *block);
-
#endif /* __BLOCKCACHE_H__ */
/*
- * Copyright (C) 2014 Paul Cercueil <paul@crapouillou.net>
+ * Copyright (C) 2014-2020 Paul Cercueil <paul@crapouillou.net>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
/*
- * Copyright (C) 2014 Paul Cercueil <paul@crapouillou.net>
+ * Copyright (C) 2014-2020 Paul Cercueil <paul@crapouillou.net>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
lightrec_regcache_mark_live(reg_cache, _jit);
- if (op->i.op == OP_CP0 && (op->r.rd == 12 || op->r.rd == 13))
+ if (op->i.op == OP_CP0 && !(op->flags & LIGHTREC_NO_DS) &&
+ (op->r.rd == 12 || op->r.rd == 13))
lightrec_emit_end_of_block(block, op, pc, -1, pc + 4, 0, 0, true);
}
op->offset << 2);
target = &state->targets[state->nb_targets++];
target->offset = op->offset;
- target->label = jit_label();
+ target->label = jit_indirect();
}
static const lightrec_rec_func_t rec_standard[64] = {
/*
- * Copyright (C) 2014 Paul Cercueil <paul@crapouillou.net>
+ * Copyright (C) 2014-2020 Paul Cercueil <paul@crapouillou.net>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* but on branch boundaries, we need to adjust the return
* address so that the GTE opcode is effectively executed.
*/
- cause = (*state->ops.cop0_ops.cfc)(state, 13);
- epc = (*state->ops.cop0_ops.cfc)(state, 14);
+ cause = (*state->ops.cop0_ops.cfc)(state, op->c.opcode, 13);
+ epc = (*state->ops.cop0_ops.cfc)(state, op->c.opcode, 14);
if (!(cause & 0x7c) && epc == pc - 4)
pc -= 4;
branch_taken = is_branch_taken(reg_cache, op_next);
pr_debug("Target of impossible branch is a branch, "
"%staken.\n", branch_taken ? "" : "not ");
+ inter->cycles += lightrec_cycles_of_opcode(op_next);
+ old_rs = reg_cache[op_next.r.rs];
} else {
new_op.c = op_next;
new_op.flags = 0;
new_rt = reg_cache[op->r.rt];
/* Execute delay slot opcode */
- if (branch_at_addr)
- ds_next_pc = int_branch(&inter2, pc, op_next, branch_taken);
- else
- ds_next_pc = (*int_standard[inter2.op->i.op])(&inter2);
+ ds_next_pc = (*int_standard[inter2.op->i.op])(&inter2);
+
+ if (branch_at_addr) {
+ if (op_next.i.op == OP_SPECIAL)
+ /* TODO: Handle JALR setting $ra */
+ ds_next_pc = old_rs;
+ else if (op_next.i.op == OP_J || op_next.i.op == OP_JAL)
+ /* TODO: Handle JAL setting $ra */
+ ds_next_pc = (pc & 0xf0000000) | (op_next.j.imm << 2);
+ else
+ ds_next_pc = pc + 4 + ((s16)op_next.i.imm << 2);
+ }
if (branch_at_addr && !branch_taken) {
/* If the branch at the target of the branch opcode is not
* taken, we jump to its delay slot */
next_pc = pc + sizeof(u32);
- } else if (!branch && branch_in_ds) {
+ } else if (branch_at_addr || (!branch && branch_in_ds)) {
next_pc = ds_next_pc;
}
/* If we have a MTC0 or CTC0 to CP0 register 12 (Status) or 13 (Cause),
* return early so that the emulator will be able to check software
* interrupt status. */
- if (op->i.op == OP_CP0 && (op->r.rd == 12 || op->r.rd == 13))
+ if (!(inter->op->flags & LIGHTREC_NO_DS) &&
+ op->i.op == OP_CP0 && (op->r.rd == 12 || op->r.rd == 13))
return inter->block->pc + (op->offset + 1) * sizeof(u32);
else
return jump_next(inter);
u32 status;
/* Read CP0 Status register (r12) */
- status = state->ops.cop0_ops.mfc(state, 12);
+ status = state->ops.cop0_ops.mfc(state, inter->op->c.opcode, 12);
/* Switch the bits */
status = ((status & 0x3c) >> 2) | (status & ~0xf);
/* Write it back */
- state->ops.cop0_ops.ctc(state, 12, status);
+ state->ops.cop0_ops.ctc(state, inter->op->c.opcode, 12, status);
return jump_next(inter);
}
/*
- * Copyright (C) 2019 Paul Cercueil <paul@crapouillou.net>
+ * Copyright (C) 2019-2020 Paul Cercueil <paul@crapouillou.net>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
/*
- * Copyright (C) 2016 Paul Cercueil <paul@crapouillou.net>
+ * Copyright (C) 2016-2020 Paul Cercueil <paul@crapouillou.net>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
#define BLOCK_NEVER_COMPILE BIT(0)
#define BLOCK_SHOULD_RECOMPILE BIT(1)
#define BLOCK_FULLY_TAGGED BIT(2)
+#define BLOCK_IS_DEAD BIT(3)
#define RAM_SIZE 0x200000
#define BIOS_SIZE 0x80000
struct regcache;
struct opcode;
struct tinymm;
+struct reaper;
struct block {
jit_state_t *_jit;
struct blockcache *block_cache;
struct regcache *reg_cache;
struct recompiler *rec;
+ struct reaper *reaper;
void (*eob_wrapper_func)(void);
void (*get_next_block)(void);
struct lightrec_ops ops;
+ unsigned int nb_precompile;
unsigned int cycles;
unsigned int nb_maps;
const struct lightrec_mem_map *maps;
#include "interpreter.h"
#include "lightrec.h"
#include "memmanager.h"
+#include "reaper.h"
#include "recompiler.h"
#include "regcache.h"
#include "optimizer.h"
static struct block * lightrec_precompile_block(struct lightrec_state *state,
u32 pc);
+static void lightrec_default_sb(struct lightrec_state *state, u32 opcode,
+ void *host, u32 addr, u8 data)
+{
+ *(u8 *)host = data;
+
+ if (!state->invalidate_from_dma_only)
+ lightrec_invalidate(state, addr, 1);
+}
+
+static void lightrec_default_sh(struct lightrec_state *state, u32 opcode,
+ void *host, u32 addr, u16 data)
+{
+ *(u16 *)host = HTOLE16(data);
+
+ if (!state->invalidate_from_dma_only)
+ lightrec_invalidate(state, addr, 2);
+}
+
+static void lightrec_default_sw(struct lightrec_state *state, u32 opcode,
+ void *host, u32 addr, u32 data)
+{
+ *(u32 *)host = HTOLE32(data);
+
+ if (!state->invalidate_from_dma_only)
+ lightrec_invalidate(state, addr, 4);
+}
+
+static u8 lightrec_default_lb(struct lightrec_state *state,
+ u32 opcode, void *host, u32 addr)
+{
+ return *(u8 *)host;
+}
+
+static u16 lightrec_default_lh(struct lightrec_state *state,
+ u32 opcode, void *host, u32 addr)
+{
+ return LE16TOH(*(u16 *)host);
+}
+
+static u32 lightrec_default_lw(struct lightrec_state *state,
+ u32 opcode, void *host, u32 addr)
+{
+ return LE32TOH(*(u32 *)host);
+}
+
+static const struct lightrec_mem_map_ops lightrec_default_ops = {
+ .sb = lightrec_default_sb,
+ .sh = lightrec_default_sh,
+ .sw = lightrec_default_sw,
+ .lb = lightrec_default_lb,
+ .lh = lightrec_default_lh,
+ .lw = lightrec_default_lw,
+};
+
static void __segfault_cb(struct lightrec_state *state, u32 addr)
{
lightrec_set_exit_flags(state, LIGHTREC_EXIT_SEGFAULT);
"load/store at address 0x%08x\n", addr);
}
-static u32 lightrec_rw_ops(struct lightrec_state *state, union code op,
- const struct lightrec_mem_map_ops *ops, u32 addr, u32 data)
+static void lightrec_swl(struct lightrec_state *state,
+ const struct lightrec_mem_map_ops *ops,
+ u32 opcode, void *host, u32 addr, u32 data)
{
- switch (op.i.op) {
- case OP_SB:
- ops->sb(state, addr, (u8) data);
- return 0;
- case OP_SH:
- ops->sh(state, addr, (u16) data);
- return 0;
- case OP_SWL:
- case OP_SWR:
- case OP_SW:
- ops->sw(state, addr, data);
- return 0;
- case OP_LB:
- return (s32) (s8) ops->lb(state, addr);
- case OP_LBU:
- return ops->lb(state, addr);
- case OP_LH:
- return (s32) (s16) ops->lh(state, addr);
- case OP_LHU:
- return ops->lh(state, addr);
- case OP_LW:
- default:
- return ops->lw(state, addr);
- }
+ unsigned int shift = addr & 0x3;
+ unsigned int mask = GENMASK(31, (shift + 1) * 8);
+ u32 old_data;
+
+ /* Align to 32 bits */
+ addr &= ~3;
+ host = (void *)((uintptr_t)host & ~3);
+
+ old_data = ops->lw(state, opcode, host, addr);
+
+ data = (data >> ((3 - shift) * 8)) | (old_data & mask);
+
+ ops->sw(state, opcode, host, addr, data);
+}
+
+static void lightrec_swr(struct lightrec_state *state,
+ const struct lightrec_mem_map_ops *ops,
+ u32 opcode, void *host, u32 addr, u32 data)
+{
+ unsigned int shift = addr & 0x3;
+ unsigned int mask = (1 << (shift * 8)) - 1;
+ u32 old_data;
+
+ /* Align to 32 bits */
+ addr &= ~3;
+ host = (void *)((uintptr_t)host & ~3);
+
+ old_data = ops->lw(state, opcode, host, addr);
+
+ data = (data << (shift * 8)) | (old_data & mask);
+
+ ops->sw(state, opcode, host, addr, data);
+}
+
+static void lightrec_swc2(struct lightrec_state *state, union code op,
+ const struct lightrec_mem_map_ops *ops,
+ void *host, u32 addr)
+{
+ u32 data = state->ops.cop2_ops.mfc(state, op.opcode, op.i.rt);
+
+ ops->sw(state, op.opcode, host, addr, data);
+}
+
+static u32 lightrec_lwl(struct lightrec_state *state,
+ const struct lightrec_mem_map_ops *ops,
+ u32 opcode, void *host, u32 addr, u32 data)
+{
+ unsigned int shift = addr & 0x3;
+ unsigned int mask = (1 << (24 - shift * 8)) - 1;
+ u32 old_data;
+
+ /* Align to 32 bits */
+ addr &= ~3;
+ host = (void *)((uintptr_t)host & ~3);
+
+ old_data = ops->lw(state, opcode, host, addr);
+
+ return (data & mask) | (old_data << (24 - shift * 8));
+}
+
+static u32 lightrec_lwr(struct lightrec_state *state,
+ const struct lightrec_mem_map_ops *ops,
+ u32 opcode, void *host, u32 addr, u32 data)
+{
+ unsigned int shift = addr & 0x3;
+ unsigned int mask = GENMASK(31, 32 - shift * 8);
+ u32 old_data;
+
+ /* Align to 32 bits */
+ addr &= ~3;
+ host = (void *)((uintptr_t)host & ~3);
+
+ old_data = ops->lw(state, opcode, host, addr);
+
+ return (data & mask) | (old_data >> (shift * 8));
+}
+
+static void lightrec_lwc2(struct lightrec_state *state, union code op,
+ const struct lightrec_mem_map_ops *ops,
+ void *host, u32 addr)
+{
+ u32 data = ops->lw(state, op.opcode, host, addr);
+
+ state->ops.cop2_ops.mtc(state, op.opcode, op.i.rt, data);
}
static void lightrec_invalidate_map(struct lightrec_state *state,
u32 addr, u32 data, u16 *flags)
{
const struct lightrec_mem_map *map;
- u32 shift, mem_data, mask, pc;
- uintptr_t new_addr;
- u32 kaddr;
+ const struct lightrec_mem_map_ops *ops;
+ u32 kaddr, pc, opcode = op.opcode;
+ void *host;
addr += (s16) op.i.imm;
kaddr = kunseg(addr);
pc = map->pc;
+ while (map->mirror_of)
+ map = map->mirror_of;
+
+ host = (void *)((uintptr_t)map->address + kaddr - pc);
+
if (unlikely(map->ops)) {
if (flags)
*flags |= LIGHTREC_HW_IO;
- return lightrec_rw_ops(state, op, map->ops, addr, data);
- }
-
- while (map->mirror_of)
- map = map->mirror_of;
-
- if (flags)
- *flags |= LIGHTREC_DIRECT_IO;
+ ops = map->ops;
+ } else {
+ if (flags)
+ *flags |= LIGHTREC_DIRECT_IO;
- kaddr -= pc;
- new_addr = (uintptr_t) map->address + kaddr;
+ ops = &lightrec_default_ops;
+ }
switch (op.i.op) {
case OP_SB:
- *(u8 *) new_addr = (u8) data;
- if (!state->invalidate_from_dma_only)
- lightrec_invalidate_map(state, map, kaddr);
+ ops->sb(state, opcode, host, addr, (u8) data);
return 0;
case OP_SH:
- *(u16 *) new_addr = HTOLE16((u16) data);
- if (!state->invalidate_from_dma_only)
- lightrec_invalidate_map(state, map, kaddr);
+ ops->sh(state, opcode, host, addr, (u16) data);
return 0;
case OP_SWL:
- shift = kaddr & 3;
- mem_data = LE32TOH(*(u32 *)(new_addr & ~3));
- mask = GENMASK(31, (shift + 1) * 8);
-
- *(u32 *)(new_addr & ~3) = HTOLE32((data >> ((3 - shift) * 8))
- | (mem_data & mask));
- if (!state->invalidate_from_dma_only)
- lightrec_invalidate_map(state, map, kaddr & ~0x3);
+ lightrec_swl(state, ops, opcode, host, addr, data);
return 0;
case OP_SWR:
- shift = kaddr & 3;
- mem_data = LE32TOH(*(u32 *)(new_addr & ~3));
- mask = (1 << (shift * 8)) - 1;
-
- *(u32 *)(new_addr & ~3) = HTOLE32((data << (shift * 8))
- | (mem_data & mask));
- if (!state->invalidate_from_dma_only)
- lightrec_invalidate_map(state, map, kaddr & ~0x3);
+ lightrec_swr(state, ops, opcode, host, addr, data);
return 0;
case OP_SW:
- *(u32 *) new_addr = HTOLE32(data);
- if (!state->invalidate_from_dma_only)
- lightrec_invalidate_map(state, map, kaddr);
+ ops->sw(state, opcode, host, addr, data);
return 0;
case OP_SWC2:
- *(u32 *) new_addr = HTOLE32(state->ops.cop2_ops.mfc(state,
- op.i.rt));
- if (!state->invalidate_from_dma_only)
- lightrec_invalidate_map(state, map, kaddr);
+ lightrec_swc2(state, op, ops, host, addr);
return 0;
case OP_LB:
- return (s32) *(s8 *) new_addr;
+ return (s32) (s8) ops->lb(state, opcode, host, addr);
case OP_LBU:
- return *(u8 *) new_addr;
+ return ops->lb(state, opcode, host, addr);
case OP_LH:
- return (s32)(s16) LE16TOH(*(u16 *) new_addr);
+ return (s32) (s16) ops->lh(state, opcode, host, addr);
case OP_LHU:
- return LE16TOH(*(u16 *) new_addr);
- case OP_LWL:
- shift = kaddr & 3;
- mem_data = LE32TOH(*(u32 *)(new_addr & ~3));
- mask = (1 << (24 - shift * 8)) - 1;
-
- return (data & mask) | (mem_data << (24 - shift * 8));
- case OP_LWR:
- shift = kaddr & 3;
- mem_data = LE32TOH(*(u32 *)(new_addr & ~3));
- mask = GENMASK(31, 32 - shift * 8);
-
- return (data & mask) | (mem_data >> (shift * 8));
+ return ops->lh(state, opcode, host, addr);
case OP_LWC2:
- state->ops.cop2_ops.mtc(state, op.i.rt,
- LE32TOH(*(u32 *) new_addr));
+ lightrec_lwc2(state, op, ops, host, addr);
return 0;
+ case OP_LWL:
+ return lightrec_lwl(state, ops, opcode, host, addr, data);
+ case OP_LWR:
+ return lightrec_lwr(state, ops, opcode, host, addr, data);
case OP_LW:
default:
- return LE32TOH(*(u32 *) new_addr);
+ return ops->lw(state, opcode, host, addr);
}
}
"tagged - flag for recompilation\n",
block->pc, op->offset << 2);
- lightrec_mark_for_recompilation(state->block_cache, block);
+ block->flags |= BLOCK_SHOULD_RECOMPILE;
}
}
{
bool is_cfc = (op.i.op == OP_CP0 && op.r.rs == OP_CP0_CFC0) ||
(op.i.op == OP_CP2 && op.r.rs == OP_CP2_BASIC_CFC2);
- u32 (*func)(struct lightrec_state *, u8);
+ u32 (*func)(struct lightrec_state *, u32, u8);
const struct lightrec_cop_ops *ops;
if (op.i.op == OP_CP0)
else
func = ops->mfc;
- return (*func)(state, op.r.rd);
+ return (*func)(state, op.opcode, op.r.rd);
}
static void lightrec_mfc_cb(struct lightrec_state *state, union code op)
{
bool is_ctc = (op.i.op == OP_CP0 && op.r.rs == OP_CP0_CTC0) ||
(op.i.op == OP_CP2 && op.r.rs == OP_CP2_BASIC_CTC2);
- void (*func)(struct lightrec_state *, u8, u32);
+ void (*func)(struct lightrec_state *, u32, u8, u32);
const struct lightrec_cop_ops *ops;
if (op.i.op == OP_CP0)
else
func = ops->mtc;
- (*func)(state, op.r.rd, data);
+ (*func)(state, op.opcode, op.r.rd, data);
}
static void lightrec_mtc_cb(struct lightrec_state *state, union code op)
u32 status;
/* Read CP0 Status register (r12) */
- status = state->ops.cop0_ops.mfc(state, 12);
+ status = state->ops.cop0_ops.mfc(state, op.opcode, 12);
/* Switch the bits */
status = ((status & 0x3c) >> 2) | (status & ~0xf);
/* Write it back */
- state->ops.cop0_ops.ctc(state, 12, status);
+ state->ops.cop0_ops.ctc(state, op.opcode, 12, status);
}
static void lightrec_cp_cb(struct lightrec_state *state, union code op)
lightrec_recompiler_remove(state->rec, block);
lightrec_unregister_block(state->block_cache, block);
+ remove_from_code_lut(state->block_cache, block);
lightrec_free_block(block);
block = NULL;
}
if (unlikely(!block))
return NULL;
- should_recompile = block->flags & BLOCK_SHOULD_RECOMPILE;
+ should_recompile = block->flags & BLOCK_SHOULD_RECOMPILE &&
+ !(block->flags & BLOCK_IS_DEAD);
if (unlikely(should_recompile)) {
- pr_debug("Block at PC 0x%08x should recompile"
- " - freeing old code\n", pc);
-
- if (ENABLE_THREADED_COMPILER)
- lightrec_recompiler_remove(state->rec, block);
+ pr_debug("Block at PC 0x%08x should recompile\n", pc);
- remove_from_code_lut(state->block_cache, block);
lightrec_unregister(MEM_FOR_CODE, block->code_size);
- if (block->_jit)
- _jit_destroy_state(block->_jit);
- block->_jit = NULL;
- block->function = NULL;
- block->flags &= ~BLOCK_SHOULD_RECOMPILE;
+
+ if (ENABLE_THREADED_COMPILER)
+ lightrec_recompiler_add(state->rec, block);
+ else
+ lightrec_compile_block(block);
}
if (ENABLE_THREADED_COMPILER && likely(!should_recompile))
block->hash = lightrec_calculate_block_hash(block);
+ pr_debug("Recompile count: %u\n", state->nb_precompile++);
+
return block;
}
return true;
}
+static void lightrec_reap_block(void *data)
+{
+ struct block *block = data;
+
+ pr_debug("Reap dead block at PC 0x%08x\n", block->pc);
+ lightrec_free_block(block);
+}
+
+static void lightrec_reap_jit(void *data)
+{
+ _jit_destroy_state(data);
+}
+
int lightrec_compile_block(struct block *block)
{
struct lightrec_state *state = block->state;
+ struct lightrec_branch_target *target;
bool op_list_freed = false, fully_tagged = false;
+ struct block *block2;
struct opcode *elm;
- jit_state_t *_jit;
+ jit_state_t *_jit, *oldjit;
jit_node_t *start_of_block;
bool skip_next = false;
jit_word_t code_size;
unsigned int i, j;
- u32 next_pc;
+ u32 next_pc, offset;
fully_tagged = lightrec_block_is_fully_tagged(block);
if (fully_tagged)
if (!_jit)
return -ENOMEM;
+ oldjit = block->_jit;
block->_jit = _jit;
lightrec_regcache_reset(state->reg_cache);
jit_epilog();
block->function = jit_emit();
+ block->flags &= ~BLOCK_SHOULD_RECOMPILE;
/* Add compiled function to the LUT */
state->code_lut[lut_offset(block->pc)] = block->function;
+ /* Fill code LUT with the block's entry points */
+ for (i = 0; i < state->nb_targets; i++) {
+ target = &state->targets[i];
+
+ if (target->offset) {
+ offset = lut_offset(block->pc) + target->offset;
+ state->code_lut[offset] = jit_address(target->label);
+ }
+ }
+
+ /* Detect old blocks that have been covered by the new one */
+ for (i = 0; i < state->nb_targets; i++) {
+ target = &state->targets[i];
+
+ if (!target->offset)
+ continue;
+
+ offset = block->pc + target->offset * sizeof(u32);
+ block2 = lightrec_find_block(state->block_cache, offset);
+ if (block2) {
+ /* No need to check if block2 is compilable - it must
+ * be, otherwise block wouldn't be compilable either */
+
+ block2->flags |= BLOCK_IS_DEAD;
+
+ pr_debug("Reap block 0x%08x as it's covered by block "
+ "0x%08x\n", block2->pc, block->pc);
+
+ lightrec_unregister_block(state->block_cache, block2);
+
+ if (ENABLE_THREADED_COMPILER) {
+ lightrec_recompiler_remove(state->rec, block2);
+ lightrec_reaper_add(state->reaper,
+ lightrec_reap_block,
+ block2);
+ } else {
+ lightrec_free_block(block2);
+ }
+ }
+ }
+
jit_get_code(&code_size);
lightrec_register(MEM_FOR_CODE, code_size);
block->opcode_list = NULL;
}
+ if (oldjit) {
+ pr_debug("Block 0x%08x recompiled, reaping old jit context.\n",
+ block->pc);
+
+ if (ENABLE_THREADED_COMPILER)
+ lightrec_reaper_add(state->reaper,
+ lightrec_reap_jit, oldjit);
+ else
+ _jit_destroy_state(oldjit);
+ }
+
return 0;
}
state->current_cycle = state->target_cycle - cycles_delta;
}
+ if (ENABLE_THREADED_COMPILER)
+ lightrec_reaper_reap(state->reaper);
+
return state->next_pc;
}
state->rec = lightrec_recompiler_init(state);
if (!state->rec)
goto err_free_reg_cache;
+
+ state->reaper = lightrec_reaper_init(state);
+ if (!state->reaper)
+ goto err_free_recompiler;
}
state->nb_maps = nb;
state->dispatcher = generate_dispatcher(state);
if (!state->dispatcher)
- goto err_free_recompiler;
+ goto err_free_reaper;
state->rw_generic_wrapper = generate_wrapper(state,
lightrec_rw_generic_cb,
lightrec_free_block(state->rw_generic_wrapper);
err_free_dispatcher:
lightrec_free_block(state->dispatcher);
+err_free_reaper:
+ if (ENABLE_THREADED_COMPILER)
+ lightrec_reaper_destroy(state->reaper);
err_free_recompiler:
if (ENABLE_THREADED_COMPILER)
lightrec_free_recompiler(state->rec);
void lightrec_destroy(struct lightrec_state *state)
{
- if (ENABLE_THREADED_COMPILER)
+ if (ENABLE_THREADED_COMPILER) {
lightrec_free_recompiler(state->rec);
+ lightrec_reaper_destroy(state->reaper);
+ }
lightrec_free_regcache(state->reg_cache);
lightrec_free_block_cache(state->block_cache);
/*
- * Copyright (C) 2016 Paul Cercueil <paul@crapouillou.net>
+ * Copyright (C) 2016-2020 Paul Cercueil <paul@crapouillou.net>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
};
struct lightrec_mem_map_ops {
- void (*sb)(struct lightrec_state *, u32 addr, u8 data);
- void (*sh)(struct lightrec_state *, u32 addr, u16 data);
- void (*sw)(struct lightrec_state *, u32 addr, u32 data);
- u8 (*lb)(struct lightrec_state *, u32 addr);
- u16 (*lh)(struct lightrec_state *, u32 addr);
- u32 (*lw)(struct lightrec_state *, u32 addr);
+ void (*sb)(struct lightrec_state *, u32 opcode,
+ void *host, u32 addr, u8 data);
+ void (*sh)(struct lightrec_state *, u32 opcode,
+ void *host, u32 addr, u16 data);
+ void (*sw)(struct lightrec_state *, u32 opcode,
+ void *host, u32 addr, u32 data);
+ u8 (*lb)(struct lightrec_state *, u32 opcode, void *host, u32 addr);
+ u16 (*lh)(struct lightrec_state *, u32 opcode, void *host, u32 addr);
+ u32 (*lw)(struct lightrec_state *, u32 opcode, void *host, u32 addr);
};
struct lightrec_mem_map {
};
struct lightrec_cop_ops {
- u32 (*mfc)(struct lightrec_state *state, u8 reg);
- u32 (*cfc)(struct lightrec_state *state, u8 reg);
- void (*mtc)(struct lightrec_state *state, u8 reg, u32 value);
- void (*ctc)(struct lightrec_state *state, u8 reg, u32 value);
- void (*op)(struct lightrec_state *state, u32 opcode);
+ u32 (*mfc)(struct lightrec_state *state, u32 op, u8 reg);
+ u32 (*cfc)(struct lightrec_state *state, u32 op, u8 reg);
+ void (*mtc)(struct lightrec_state *state, u32 op, u8 reg, u32 value);
+ void (*ctc)(struct lightrec_state *state, u32 op, u8 reg, u32 value);
+ void (*op)(struct lightrec_state *state, u32 op);
};
struct lightrec_ops {
/*
- * Copyright (C) 2019 Paul Cercueil <paul@crapouillou.net>
+ * Copyright (C) 2019-2020 Paul Cercueil <paul@crapouillou.net>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
list->c = next_op;
list->next->c = op;
list->next->flags = list->flags | LIGHTREC_NO_DS;
- list->flags = flags;
+ list->flags = flags | LIGHTREC_NO_DS;
list->offset++;
list->next->offset--;
}
case OP_SB:
case OP_SH:
case OP_SW:
- /* Mark all store operations that target $sp, $gp, $k0
- * or $k1 as not requiring code invalidation. This is
- * based on the heuristic that stores using one of these
+ /* Mark all store operations that target $sp or $gp
+ * as not requiring code invalidation. This is based
+ * on the heuristic that stores using one of these
* registers as address will never hit a code page. */
- if (list->i.rs >= 26 && list->i.rs <= 29) {
+ if (list->i.rs >= 28 && list->i.rs <= 29 &&
+ !block->state->maps[PSX_MAP_KERNEL_USER_RAM].ops) {
pr_debug("Flaging opcode 0x%08x as not requiring invalidation\n",
list->opcode);
list->flags |= LIGHTREC_NO_INVALIDATE;
/*
- * Copyright (C) 2014 Paul Cercueil <paul@crapouillou.net>
+ * Copyright (C) 2014-2020 Paul Cercueil <paul@crapouillou.net>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
--- /dev/null
+/*
+ * Copyright (C) 2020 Paul Cercueil <paul@crapouillou.net>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ */
+
+#include "blockcache.h"
+#include "debug.h"
+#include "lightrec-private.h"
+#include "memmanager.h"
+#include "slist.h"
+#include "reaper.h"
+
+#include <errno.h>
+#include <pthread.h>
+#include <stdbool.h>
+
+struct reaper_elm {
+ reap_func_t func;
+ void *data;
+ struct slist_elm slist;
+};
+
+struct reaper {
+ struct lightrec_state *state;
+ pthread_mutex_t mutex;
+ struct slist_elm reap_list;
+};
+
+struct reaper *lightrec_reaper_init(struct lightrec_state *state)
+{
+ struct reaper *reaper;
+ int ret;
+
+ reaper = lightrec_malloc(state, MEM_FOR_LIGHTREC, sizeof(*reaper));
+ if (!reaper) {
+ pr_err("Cannot create reaper: Out of memory\n");
+ return NULL;
+ }
+
+ reaper->state = state;
+ slist_init(&reaper->reap_list);
+
+ ret = pthread_mutex_init(&reaper->mutex, NULL);
+ if (ret) {
+ pr_err("Cannot init mutex variable: %d\n", ret);
+ lightrec_free(reaper->state, MEM_FOR_LIGHTREC,
+ sizeof(*reaper), reaper);
+ return NULL;
+ }
+
+ return reaper;
+}
+
+void lightrec_reaper_destroy(struct reaper *reaper)
+{
+ pthread_mutex_destroy(&reaper->mutex);
+ lightrec_free(reaper->state, MEM_FOR_LIGHTREC, sizeof(*reaper), reaper);
+}
+
+int lightrec_reaper_add(struct reaper *reaper, reap_func_t f, void *data)
+{
+ struct reaper_elm *reaper_elm;
+ struct slist_elm *elm;
+ int ret = 0;
+
+ pthread_mutex_lock(&reaper->mutex);
+
+ for (elm = reaper->reap_list.next; elm; elm = elm->next) {
+ reaper_elm = container_of(elm, struct reaper_elm, slist);
+
+ if (reaper_elm->data == data)
+ goto out_unlock;
+ }
+
+ reaper_elm = lightrec_malloc(reaper->state, MEM_FOR_LIGHTREC,
+ sizeof(*reaper_elm));
+ if (!reaper_elm) {
+ pr_err("Cannot add reaper entry: Out of memory\n");
+ ret = -ENOMEM;
+ goto out_unlock;
+ }
+
+ reaper_elm->func = f;
+ reaper_elm->data = data;
+ slist_append(&reaper->reap_list, &reaper_elm->slist);
+
+out_unlock:
+ pthread_mutex_unlock(&reaper->mutex);
+ return ret;
+}
+
+void lightrec_reaper_reap(struct reaper *reaper)
+{
+ struct reaper_elm *reaper_elm;
+ struct slist_elm *elm;
+
+ pthread_mutex_lock(&reaper->mutex);
+
+ while (!!(elm = slist_first(&reaper->reap_list))) {
+ slist_remove(&reaper->reap_list, elm);
+ pthread_mutex_unlock(&reaper->mutex);
+
+ reaper_elm = container_of(elm, struct reaper_elm, slist);
+
+ (*reaper_elm->func)(reaper_elm->data);
+
+ lightrec_free(reaper->state, MEM_FOR_LIGHTREC,
+ sizeof(*reaper_elm), reaper_elm);
+
+ pthread_mutex_lock(&reaper->mutex);
+ }
+
+ pthread_mutex_unlock(&reaper->mutex);
+}
/*
- * Copyright (C) 2019 Paul Cercueil <paul@crapouillou.net>
+ * Copyright (C) 2020 Paul Cercueil <paul@crapouillou.net>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* Lesser General Public License for more details.
*/
-#ifndef __LIGHTREC_CONFIG_H__
-#define __LIGHTREC_CONFIG_H__
+#ifndef __LIGHTREC_REAPER_H__
+#define __LIGHTREC_REAPER_H__
-#define ENABLE_THREADED_COMPILER 1
-#define ENABLE_FIRST_PASS 1
-#define ENABLE_DISASSEMBLER 0
-#define ENABLE_TINYMM 0
+struct lightrec_state;
+struct reaper;
-#endif /* __LIGHTREC_CONFIG_H__ */
+typedef void (*reap_func_t)(void *);
+struct reaper *lightrec_reaper_init(struct lightrec_state *state);
+void lightrec_reaper_destroy(struct reaper *reaper);
+
+int lightrec_reaper_add(struct reaper *reaper, reap_func_t f, void *data);
+void lightrec_reaper_reap(struct reaper *reaper);
+
+#endif /* __LIGHTREC_REAPER_H__ */
#include "interpreter.h"
#include "lightrec-private.h"
#include "memmanager.h"
+#include "slist.h"
#include <errno.h>
#include <stdatomic.h>
struct block_rec {
struct block *block;
- struct block_rec *next;
+ struct slist_elm slist;
};
struct recompiler {
pthread_mutex_t mutex;
bool stop;
struct block *current_block;
- struct block_rec *list;
+ struct slist_elm slist;
};
-static void slist_remove(struct recompiler *rec, struct block_rec *elm)
-{
- struct block_rec *prev;
-
- if (rec->list == elm) {
- rec->list = elm->next;
- } else {
- for (prev = rec->list; prev && prev->next != elm; )
- prev = prev->next;
- if (prev)
- prev->next = elm->next;
- }
-}
-
static void lightrec_compile_list(struct recompiler *rec)
{
- struct block_rec *next;
+ struct block_rec *block_rec;
+ struct slist_elm *next;
struct block *block;
int ret;
- while (!!(next = rec->list)) {
- block = next->block;
+ while (!!(next = slist_first(&rec->slist))) {
+ block_rec = container_of(next, struct block_rec, slist);
+ block = block_rec->block;
rec->current_block = block;
pthread_mutex_unlock(&rec->mutex);
pthread_mutex_lock(&rec->mutex);
- slist_remove(rec, next);
+ slist_remove(&rec->slist, next);
lightrec_free(rec->state, MEM_FOR_LIGHTREC,
- sizeof(*next), next);
+ sizeof(*block_rec), block_rec);
pthread_cond_signal(&rec->cond);
}
pthread_mutex_lock(&rec->mutex);
- for (;;) {
+ do {
do {
pthread_cond_wait(&rec->cond, &rec->mutex);
- if (rec->stop) {
- pthread_mutex_unlock(&rec->mutex);
- return NULL;
- }
+ if (rec->stop)
+ goto out_unlock;
- } while (!rec->list);
+ } while (slist_empty(&rec->slist));
lightrec_compile_list(rec);
- }
+ } while (!rec->stop);
+
+out_unlock:
+ pthread_mutex_unlock(&rec->mutex);
+ return NULL;
}
struct recompiler *lightrec_recompiler_init(struct lightrec_state *state)
rec->state = state;
rec->stop = false;
rec->current_block = NULL;
- rec->list = NULL;
+ slist_init(&rec->slist);
ret = pthread_cond_init(&rec->cond, NULL);
if (ret) {
int lightrec_recompiler_add(struct recompiler *rec, struct block *block)
{
- struct block_rec *block_rec, *prev;
+ struct slist_elm *elm, *prev;
+ struct block_rec *block_rec;
+ int ret = 0;
pthread_mutex_lock(&rec->mutex);
- for (block_rec = rec->list, prev = NULL; block_rec;
- prev = block_rec, block_rec = block_rec->next) {
+ /* If the block is marked as dead, don't compile it, it will be removed
+ * as soon as it's safe. */
+ if (block->flags & BLOCK_IS_DEAD)
+ goto out_unlock;
+
+ for (elm = slist_first(&rec->slist), prev = NULL; elm;
+ prev = elm, elm = elm->next) {
+ block_rec = container_of(elm, struct block_rec, slist);
+
if (block_rec->block == block) {
/* The block to compile is already in the queue - bump
- * it to the top of the list */
- if (prev) {
- prev->next = block_rec->next;
- block_rec->next = rec->list;
- rec->list = block_rec;
+ * it to the top of the list, unless the block is being
+ * recompiled. */
+ if (prev && !(block->flags & BLOCK_SHOULD_RECOMPILE)) {
+ slist_remove_next(prev);
+ slist_append(&rec->slist, elm);
}
- pthread_mutex_unlock(&rec->mutex);
- return 0;
+ goto out_unlock;
}
}
/* By the time this function was called, the block has been recompiled
* and ins't in the wait list anymore. Just return here. */
- if (block->function) {
- pthread_mutex_unlock(&rec->mutex);
- return 0;
- }
+ if (block->function && !(block->flags & BLOCK_SHOULD_RECOMPILE))
+ goto out_unlock;
block_rec = lightrec_malloc(rec->state, MEM_FOR_LIGHTREC,
sizeof(*block_rec));
if (!block_rec) {
- pthread_mutex_unlock(&rec->mutex);
- return -ENOMEM;
+ ret = -ENOMEM;
+ goto out_unlock;
}
pr_debug("Adding block PC 0x%x to recompiler\n", block->pc);
block_rec->block = block;
- block_rec->next = rec->list;
- rec->list = block_rec;
+
+ elm = &rec->slist;
+
+ /* If the block is being recompiled, push it to the end of the queue;
+ * otherwise push it to the front of the queue. */
+ if (block->flags & BLOCK_SHOULD_RECOMPILE)
+ for (; elm->next; elm = elm->next);
+
+ slist_append(elm, &block_rec->slist);
/* Signal the thread */
pthread_cond_signal(&rec->cond);
- pthread_mutex_unlock(&rec->mutex);
- return 0;
+out_unlock:
+ pthread_mutex_unlock(&rec->mutex);
+ return ret;
}
void lightrec_recompiler_remove(struct recompiler *rec, struct block *block)
{
struct block_rec *block_rec;
+ struct slist_elm *elm;
pthread_mutex_lock(&rec->mutex);
- for (block_rec = rec->list; block_rec; block_rec = block_rec->next) {
+ for (elm = slist_first(&rec->slist); elm; elm = elm->next) {
+ block_rec = container_of(elm, struct block_rec, slist);
+
if (block_rec->block == block) {
if (block == rec->current_block) {
/* Block is being recompiled - wait for
} else {
/* Block is not yet being processed - remove it
* from the list */
- slist_remove(rec, block_rec);
+ slist_remove(&rec->slist, elm);
lightrec_free(rec->state, MEM_FOR_LIGHTREC,
sizeof(*block_rec), block_rec);
}
/*
- * Copyright (C) 2019 Paul Cercueil <paul@crapouillou.net>
+ * Copyright (C) 2019-2020 Paul Cercueil <paul@crapouillou.net>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
/*
- * Copyright (C) 2014 Paul Cercueil <paul@crapouillou.net>
+ * Copyright (C) 2014-2020 Paul Cercueil <paul@crapouillou.net>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
--- /dev/null
+/*
+ * Copyright (C) 2020 Paul Cercueil <paul@crapouillou.net>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ */
+
+#ifndef __LIGHTREC_SLIST_H__
+#define __LIGHTREC_SLIST_H__
+
+#define container_of(ptr, type, member) \
+ ((type *)((void *)(ptr) - offsetof(type, member)))
+
+struct slist_elm {
+ struct slist_elm *next;
+};
+
+static inline void slist_init(struct slist_elm *head)
+{
+ head->next = NULL;
+}
+
+static inline struct slist_elm * slist_first(struct slist_elm *head)
+{
+ return head->next;
+}
+
+static inline _Bool slist_empty(const struct slist_elm *head)
+{
+ return head->next == NULL;
+}
+
+static inline void slist_remove_next(struct slist_elm *elm)
+{
+ if (elm->next)
+ elm->next = elm->next->next;
+}
+
+static inline void slist_remove(struct slist_elm *head, struct slist_elm *elm)
+{
+ struct slist_elm *prev;
+
+ if (head->next == elm) {
+ head->next = elm->next;
+ } else {
+ for (prev = head->next; prev && prev->next != elm; )
+ prev = prev->next;
+ if (prev)
+ slist_remove_next(prev);
+ }
+}
+
+static inline void slist_append(struct slist_elm *head, struct slist_elm *elm)
+{
+ elm->next = head->next;
+ head->next = elm;
+}
+
+#endif /* __LIGHTREC_SLIST_H__ */