Skip to content

Commit

Permalink
n64: access compiled blocks via a hash
Browse files Browse the repository at this point in the history
We'd like the recompiler to take the execution context such as kernel
mode into account when compiling blocks. That's why it's necessary to
identify blocks not just by address but all the information used at
compile time. This is done by computing a 32-bit key and using that as
a block's identifier instead of the last six physical address bits like
was done before.

Since we have now 32-bit instead of 6-bit keys, the block() function
hashes the keys before mapping them to one of the 64 pool rows. The hash
function was chosen arbitrarily to be better than a simple multiplicative
hash and is likely not the best choice for this exact task.
  • Loading branch information
kannoneer committed Sep 8, 2024
1 parent f43da41 commit 616b3b6
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 10 deletions.
10 changes: 9 additions & 1 deletion ares/n64/cpu/cpu.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,15 @@ auto CPU::instruction() -> void {

if(Accuracy::CPU::Recompiler && recompiler.enabled && access.cache) {
if(vaddrAlignedError<Word>(access.vaddr, false)) return;
auto block = recompiler.block(ipu.pc, access.paddr, GDB::server.hasBreakpoints());
auto block = recompiler.block(ipu.pc, access.paddr,
{
.singleInstruction = GDB::server.hasBreakpoints(),
.endian = Context::Endian(context.endian),
.mode = Context::Mode(context.mode),
.cop1Enabled = scc.status.enable.coprocessor1 > 0,
.floatingPointMode = scc.status.floatingPointMode > 0,
.is64bit = context.bits == 64,
});
block->execute(*this);
} else {
auto data = fetch(access);
Expand Down
21 changes: 18 additions & 3 deletions ares/n64/cpu/cpu.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -863,7 +863,20 @@ struct CPU : Thread {
};

struct Pool {
Block* blocks[1 << 6];
struct Row {
Block* block;
u32 tag;
};
Row rows[1 << 6];
};

struct JITContext {
bool singleInstruction;
Context::Endian endian;
Context::Mode mode;
bool cop1Enabled;
bool floatingPointMode;
bool is64bit;
};

auto reset() -> void {
Expand Down Expand Up @@ -899,9 +912,11 @@ struct CPU : Thread {
}

auto pool(u32 address) -> Pool*;
auto block(u64 vaddr, u32 address, bool singleInstruction = false) -> Block*;
auto computePoolKey(u32 address, JITContext ctx) -> u32;
auto computePoolRow(u32 key) -> u32;
auto block(u64 vaddr, u32 address, JITContext ctx) -> Block*;

auto emit(u64 vaddr, u32 address, bool singleInstruction = false) -> Block*;
auto emit(u64 vaddr, u32 address, JITContext ctx) -> Block*;
auto emitZeroClear(u32 n) -> void;
auto emitEXECUTE(u32 instruction) -> bool;
auto emitSPECIAL(u32 instruction) -> bool;
Expand Down
43 changes: 37 additions & 6 deletions ares/n64/cpu/recompiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,41 @@ auto CPU::Recompiler::pool(u32 address) -> Pool* {
return pool;
}

auto CPU::Recompiler::block(u64 vaddr, u32 address, bool singleInstruction) -> Block* {
if(auto block = pool(address)->blocks[address >> 2 & 0x3f]) return block;
auto block = emit(vaddr, address, singleInstruction);
pool(address)->blocks[address >> 2 & 0x3f] = block;
auto CPU::Recompiler::computePoolKey(u32 address, JITContext ctx) -> u32 {
u32 key = address >> 2 & 0x3f;
key |= ctx.singleInstruction ? 1 << 6 : 0;
key |= ctx.endian ? 1 << 7 : 0;
key |= (ctx.mode & 0x03) << 9;
key |= ctx.cop1Enabled ? 1 << 10 : 0;
key |= ctx.floatingPointMode ? 1 << 11 : 0;
key |= ctx.is64bit ? 1 << 12 : 0;
return key;
}

auto CPU::Recompiler::computePoolRow(u32 key) -> u32 {
// Jon Maiga's 'xmx' mixer, see https://jonkagstrom.com/bit-mixer-construction/
u64 x = key;
x ^= x >> 23;
x *= 0xff51afd7ed558ccdull;
x ^= x >> 23;
u32 row = x & 0x3f;
assert(row < sizeof(Pool::rows)/sizeof(Pool::rows[0]));
return row;
}

auto CPU::Recompiler::block(u64 vaddr, u32 address, JITContext ctx) -> Block* {
u32 key = computePoolKey(address, ctx);
u32 row = computePoolRow(key);

if (pool(address)->rows[row].tag == key) {
if (auto block = pool(address)->rows[row].block) {
return block;
}
}

memory::jitprotect(false);
auto block = emit(vaddr, address, ctx);
pool(address)->rows[row] = {.block = block, .tag = key};
memory::jitprotect(true);
return block;
}
Expand All @@ -21,7 +52,7 @@ auto CPU::Recompiler::block(u64 vaddr, u32 address, bool singleInstruction) -> B
#define IpuReg(r) sreg(1), offsetof(IPU, r) - IpuBase
#define PipelineReg(x) mem(sreg(0), offsetof(CPU, pipeline) + offsetof(Pipeline, x))

auto CPU::Recompiler::emit(u64 vaddr, u32 address, bool singleInstruction) -> Block* {
auto CPU::Recompiler::emit(u64 vaddr, u32 address, JITContext ctx) -> Block* {
if(unlikely(allocator.available() < 1_MiB)) {
print("CPU allocator flush\n");
allocator.release();
Expand Down Expand Up @@ -60,7 +91,7 @@ auto CPU::Recompiler::emit(u64 vaddr, u32 address, bool singleInstruction) -> Bl
vaddr += 4;
address += 4;
jumpToSelf += 4;
if(hasBranched || (address & 0xfc) == 0 || singleInstruction) break; //block boundary
if(hasBranched || (address & 0xfc) == 0 || ctx.singleInstruction) break; //block boundary
hasBranched = branched;
jumpEpilog(flag_nz);
}
Expand Down

0 comments on commit 616b3b6

Please sign in to comment.