Rewrite interrupts handling code so that the emulator can work with a

predecode cache. This implies to run in interpreted mode only while
processing EmulOps or other native (nested) runs.

Note that the FLIGHT_RECORDER with a predecode cache gets slower than
without caching at all.
This commit is contained in:
gbeauche 2003-10-26 13:59:04 +00:00
parent d766049d59
commit 60d34a6816
5 changed files with 205 additions and 157 deletions

View File

@ -78,8 +78,9 @@
# define ROM_IS_WRITE_PROTECTED 1
#endif
// Configure PowerPC emulator
#define PPC_CHECK_INTERRUPTS 1
#define PPC_NO_LAZY_PC_UPDATE 1
#define PPC_NO_DECODE_CACHE 1
//#define PPC_NO_DECODE_CACHE 1
#define PPC_FLIGHT_RECORDER 1
#else
// Mac ROM is write protected

View File

@ -44,7 +44,7 @@
#include "mon_disass.h"
#endif
#define DEBUG 1
#define DEBUG 0
#include "debug.h"
static void enter_mon(void)
@ -89,16 +89,15 @@ class sheepshaver_cpu
public:
sheepshaver_cpu()
: powerpc_cpu()
{ init_decoder(); }
// Constructor
sheepshaver_cpu();
// Condition Register accessors
uint32 get_cr() const { return cr().get(); }
void set_cr(uint32 v) { cr().set(v); }
// Execution loop
void execute(uint32 pc);
void execute(uint32 entry, bool enable_cache = false);
// Execute 68k routine
void execute_68k(uint32 entry, M68kRegisters *r);
@ -114,6 +113,7 @@ public:
// Handle MacOS interrupt
void interrupt(uint32 entry);
void handle_interrupt();
// spcflags for interrupts handling
static uint32 spcflags;
@ -131,6 +131,12 @@ public:
uint32 sheepshaver_cpu::spcflags = 0;
lazy_allocator< sheepshaver_cpu > allocator_helper< sheepshaver_cpu, lazy_allocator >::allocator;
sheepshaver_cpu::sheepshaver_cpu()
: powerpc_cpu()
{
init_decoder();
}
void sheepshaver_cpu::init_decoder()
{
#ifndef PPC_NO_STATIC_II_INDEX_TABLE
@ -216,38 +222,11 @@ void sheepshaver_cpu::execute_sheep(uint32 opcode)
}
}
// Checks for pending interrupts
struct execute_nothing {
static inline void execute(powerpc_cpu *) { }
};
struct execute_spcflags_check {
static inline void execute(powerpc_cpu *cpu) {
#if !ASYNC_IRQ
if (SPCFLAGS_TEST(SPCFLAG_ALL_BUT_EXEC_RETURN)) {
if (SPCFLAGS_TEST( SPCFLAG_ENTER_MON )) {
SPCFLAGS_CLEAR( SPCFLAG_ENTER_MON );
enter_mon();
}
if (SPCFLAGS_TEST( SPCFLAG_DOINT )) {
SPCFLAGS_CLEAR( SPCFLAG_DOINT );
HandleInterrupt();
}
if (SPCFLAGS_TEST( SPCFLAG_INT )) {
SPCFLAGS_CLEAR( SPCFLAG_INT );
SPCFLAGS_SET( SPCFLAG_DOINT );
}
}
#endif
}
};
// Execution loop
void sheepshaver_cpu::execute(uint32 entry)
void sheepshaver_cpu::execute(uint32 entry, bool enable_cache)
{
try {
pc() = entry;
powerpc_cpu::do_execute<execute_nothing, execute_spcflags_check>();
powerpc_cpu::execute(entry, enable_cache);
}
catch (sheepshaver_exec_return const &) {
// Nothing, simply return
@ -596,8 +575,11 @@ void init_emul_ppc(void)
void emul_ppc(uint32 entry)
{
current_cpu = main_cpu;
#if DEBUG
current_cpu->start_log();
current_cpu->execute(entry);
#endif
// start emulation loop and enable code translation or caching
current_cpu->execute(entry, true);
}
/*
@ -610,12 +592,14 @@ void TriggerInterrupt(void)
#if 0
WriteMacInt32(0x16a, ReadMacInt32(0x16a) + 1);
#else
SPCFLAGS_SET( SPCFLAG_INT );
// Trigger interrupt to main cpu only
if (main_cpu)
main_cpu->trigger_interrupt();
#endif
}
#endif
void HandleInterrupt(void)
void sheepshaver_cpu::handle_interrupt(void)
{
// Do nothing if interrupts are disabled
if (int32(ReadMacInt32(XLM_IRQ_NEST)) > 0)
@ -634,14 +618,14 @@ void HandleInterrupt(void)
// 68k emulator active, trigger 68k interrupt level 1
assert(current_cpu == main_cpu);
WriteMacInt16(tswap32(kernel_data->v[0x67c >> 2]), 1);
main_cpu->set_cr(main_cpu->get_cr() | tswap32(kernel_data->v[0x674 >> 2]));
set_cr(get_cr() | tswap32(kernel_data->v[0x674 >> 2]));
break;
#if INTERRUPTS_IN_NATIVE_MODE
case MODE_NATIVE:
// 68k emulator inactive, in nanokernel?
assert(current_cpu == main_cpu);
if (main_cpu->gpr(1) != KernelDataAddr) {
if (gpr(1) != KernelDataAddr) {
// Prepare for 68k interrupt level 1
WriteMacInt16(tswap32(kernel_data->v[0x67c >> 2]), 1);
WriteMacInt32(tswap32(kernel_data->v[0x658 >> 2]) + 0xdc,

View File

@ -21,6 +21,22 @@
#ifndef PPC_CONFIG_H
#define PPC_CONFIG_H
/**
* PPC_CHECK_INTERRUPTS
*
* Define if interrupts need to be check after each instruction,
* in interpreted mode, or at the end of each block, in compiled
* mode.
*
* NOTE: this only checks for user defined interrupts that are
* triggered by the program. This is not about OEA interrupts.
*/
#ifndef PPC_CHECK_INTERRUPTS
#define PPC_CHECK_INTERRUPTS 0
#endif
/**
* PPC_NO_BASIC_CPU_BASE
*

View File

@ -208,8 +208,14 @@ void powerpc_cpu::initialize()
init_registers();
init_decode_cache();
// Init interrupts state
#if PPC_CHECK_INTERRUPTS
pending_interrupts = INTERRUPT_NONE;
#endif
// Init cache range invalidate recorder
cache_range.start = cache_range.end = 0;
invalidated_cache = false;
// Init syscalls handler
execute_do_syscall = NULL;
@ -275,13 +281,125 @@ void powerpc_cpu::fake_dump_registers(uint32)
dump_registers();
}
struct execute_nothing {
static inline void execute(powerpc_cpu *) { }
};
void powerpc_cpu::execute(uint32 entry, bool enable_cache)
{
pc() = entry;
#ifdef PPC_EXECUTE_DUMP_STATE
const bool dump_state = true;
#endif
#ifndef PPC_NO_DECODE_CACHE
if (enable_cache) {
for (;;) {
block_info *bi = block_cache.new_blockinfo();
bi->init(pc());
// Predecode a new block
block_info::decode_info *di = bi->di = decode_cache_p;
const instr_info_t *ii;
uint32 dpc = pc() - 4;
do {
uint32 opcode = vm_read_memory_4(dpc += 4);
ii = decode(opcode);
#ifdef PPC_EXECUTE_DUMP_STATE
if (dump_state) {
di->opcode = opcode;
di->execute = &powerpc_cpu::dump_instruction;
}
#endif
#if PPC_FLIGHT_RECORDER
if (is_logging()) {
di->opcode = opcode;
di->execute = &powerpc_cpu::record_step;
di++;
}
#endif
di->opcode = opcode;
di->execute = ii->execute;
di++;
#ifdef PPC_EXECUTE_DUMP_STATE
if (dump_state) {
di->opcode = 0;
di->execute = &powerpc_cpu::fake_dump_registers;
di++;
}
#endif
if (di >= decode_cache_end_p) {
// Invalidate cache and move current code to start
invalidate_cache();
const int blocklen = di - bi->di;
memmove(decode_cache_p, bi->di, blocklen * sizeof(*di));
bi->di = decode_cache_p;
di = bi->di + blocklen;
}
} while ((ii->cflow & CFLOW_END_BLOCK) == 0);
#ifdef PPC_LAZY_PC_UPDATE
bi->end_pc = dpc;
#endif
bi->size = di - bi->di;
block_cache.add_to_cl_list(bi);
block_cache.add_to_active_list(bi);
decode_cache_p += bi->size;
// Execute all cached blocks
invalidated_cache = false;
for (;;) {
#ifdef PPC_LAZY_PC_UPDATE
pc() = bi->end_pc;
#endif
di = bi->di;
#ifdef PPC_NO_DECODE_CACHE_UNROLL_EXECUTE
for (int i = 0; i < bi->size; i++)
(this->*(di[i].execute))(di[i].opcode);
#else
const int r = bi->size % 4;
switch (r) {
case 3: (this->*(di->execute))(di->opcode); di++;
case 2: (this->*(di->execute))(di->opcode); di++;
case 1: (this->*(di->execute))(di->opcode); di++;
case 0: break;
}
const int n = bi->size / 4;
for (int i = 0; i < n; i++) {
(this->*(di[0].execute))(di[0].opcode);
(this->*(di[1].execute))(di[1].opcode);
(this->*(di[2].execute))(di[2].opcode);
(this->*(di[3].execute))(di[3].opcode);
di += 4;
}
#endif
check_pending_interrupts();
if (invalidated_cache || ((bi->pc != pc()) && ((bi = block_cache.find(pc())) == NULL)))
break;
}
}
return;
}
#endif
for (;;) {
uint32 opcode = vm_read_memory_4(pc());
const instr_info_t *ii = decode(opcode);
#ifdef PPC_EXECUTE_DUMP_STATE
if (dump_state)
dump_instruction(opcode);
#endif
#if PPC_FLIGHT_RECORDER
if (is_logging())
record_step(opcode);
#endif
assert(ii->execute != 0);
(this->*(ii->execute))(opcode);
#ifdef PPC_EXECUTE_DUMP_STATE
if (dump_state)
dump_registers();
#endif
check_pending_interrupts();
}
}
void powerpc_cpu::execute()
{
do_execute<execute_nothing, execute_nothing>();
execute(pc());
}
void powerpc_cpu::init_decode_cache()
@ -322,6 +440,7 @@ void powerpc_cpu::invalidate_cache()
block_cache.clear();
block_cache.initialize();
decode_cache_p = decode_cache;
invalidated_cache = true;
#endif
}

View File

@ -223,9 +223,25 @@ public:
void fake_dump_registers(uint32);
// Start emulation loop
template< class prologue, class epilogue >
void do_execute();
void execute(uint32 entry, bool enable_cache = true);
void execute();
// Interrupts handling
protected:
enum {
INTERRUPT_NONE = 0,
INTERRUPT_TRIGGER = 1,
INTERRUPT_HANDLE = 2
};
#if PPC_CHECK_INTERRUPTS
int pending_interrupts;
#else
static const int pending_interrupts = 0;
#endif
public:
void check_pending_interrupts();
void trigger_interrupt();
virtual void handle_interrupt() { }
// Set VALUE to register ID
void set_register(int id, any_register const & value);
@ -241,6 +257,7 @@ public:
void invalidate_cache_range(uintptr start, uintptr end);
private:
struct { uintptr start, end; } cache_range;
bool invalidated_cache;
protected:
@ -340,119 +357,30 @@ private:
void execute_dcbz(uint32 opcode);
};
template< class prologue, class epilogue >
inline void powerpc_cpu::do_execute()
/**
* Interrupts handling
**/
inline void powerpc_cpu::trigger_interrupt()
{
#ifdef PPC_EXECUTE_DUMP_STATE
const bool dump_state = true;
#endif
#ifdef PPC_NO_DECODE_CACHE
for (;;) {
prologue::execute(this);
uint32 opcode = vm_read_memory_4(pc());
const instr_info_t *ii = decode(opcode);
#ifdef PPC_EXECUTE_DUMP_STATE
if (dump_state)
dump_instruction(opcode);
#endif
#if PPC_FLIGHT_RECORDER
if (is_logging())
record_step(opcode);
#endif
assert(ii->execute != 0);
(this->*(ii->execute))(opcode);
#ifdef PPC_EXECUTE_DUMP_STATE
if (dump_state)
dump_registers();
#endif
epilogue::execute(this);
}
#else
for (;;) {
block_info *bi = block_cache.new_blockinfo();
bi->init(pc());
// Predecode a new block
block_info::decode_info *di = bi->di = decode_cache_p;
const instr_info_t *ii;
uint32 dpc = pc() - 4;
do {
uint32 opcode = vm_read_memory_4(dpc += 4);
ii = decode(opcode);
#ifdef PPC_EXECUTE_DUMP_STATE
if (dump_state) {
di->opcode = opcode;
di->execute = &powerpc_cpu::dump_instruction;
}
#endif
#if PPC_FLIGHT_RECORDER
if (is_logging()) {
di->opcode = opcode;
di->execute = &powerpc_cpu::record_step;
di++;
}
#endif
di->opcode = opcode;
di->execute = ii->execute;
di++;
#ifdef PPC_EXECUTE_DUMP_STATE
if (dump_state) {
di->opcode = 0;
di->execute = &powerpc_cpu::fake_dump_registers;
di++;
}
#endif
if (di >= decode_cache_end_p) {
// Invalidate cache and move current code to start
invalidate_cache();
const int blocklen = di - bi->di;
memmove(decode_cache_p, bi->di, blocklen * sizeof(*di));
bi->di = decode_cache_p;
di = bi->di + blocklen;
}
} while ((ii->cflow & CFLOW_END_BLOCK) == 0);
#ifdef PPC_LAZY_PC_UPDATE
bi->end_pc = dpc;
#endif
bi->size = di - bi->di;
block_cache.add_to_cl_list(bi);
block_cache.add_to_active_list(bi);
decode_cache_p += bi->size;
// Execute all cached blocks
for (;;) {
prologue::execute(this);
#ifdef PPC_LAZY_PC_UPDATE
pc() = bi->end_pc;
#endif
di = bi->di;
#ifdef PPC_NO_DECODE_CACHE_UNROLL_EXECUTE
for (int i = 0; i < bi->size; i++)
(this->*(di[i].execute))(di[i].opcode);
#else
const int r = bi->size % 4;
switch (r) {
case 3: (this->*(di->execute))(di->opcode); di++;
case 2: (this->*(di->execute))(di->opcode); di++;
case 1: (this->*(di->execute))(di->opcode); di++;
case 0: break;
}
const int n = bi->size / 4;
for (int i = 0; i < n; i++) {
(this->*(di[0].execute))(di[0].opcode);
(this->*(di[1].execute))(di[1].opcode);
(this->*(di[2].execute))(di[2].opcode);
(this->*(di[3].execute))(di[3].opcode);
di += 4;
}
#endif
epilogue::execute(this);
if ((bi->pc != pc()) && ((bi = block_cache.find(pc())) == NULL))
break;
}
}
#if PPC_CHECK_INTERRUPTS
pending_interrupts |= INTERRUPT_TRIGGER;
#endif
}
inline void powerpc_cpu::check_pending_interrupts()
{
if (pending_interrupts) {
if (pending_interrupts & INTERRUPT_HANDLE) {
pending_interrupts &= ~INTERRUPT_HANDLE;
handle_interrupt();
}
if (pending_interrupts & INTERRUPT_TRIGGER) {
pending_interrupts &= ~INTERRUPT_TRIGGER;
pending_interrupts |= INTERRUPT_HANDLE;
}
}
}
#endif /* PPC_CPU_H */