GBA: Implement idle loop detection

This commit is contained in:
Jeffrey Pfau 2015-01-10 14:20:09 -08:00
parent 293831e3bf
commit 54ef61f715
4 changed files with 101 additions and 2 deletions

View File

@ -16,6 +16,7 @@ Features:
- Rewinding of emulation - Rewinding of emulation
- Implemented BIOS routines SoftReset, RegisterRamReset, Diff8bitUnFilterWram, Diff8bitUnFilterVram, and Diff16bitUnFilter - Implemented BIOS routines SoftReset, RegisterRamReset, Diff8bitUnFilterWram, Diff8bitUnFilterVram, and Diff16bitUnFilter
- Support IPv6 - Support IPv6
- Automatically detect and optimize out idle loops
Bugfixes: Bugfixes:
- Qt: Fix issue with set frame sizes being the wrong height - Qt: Fix issue with set frame sizes being the wrong height
- Qt: Fix emulator crashing when full screen if a game is not running - Qt: Fix emulator crashing when full screen if a game is not running

View File

@ -555,6 +555,7 @@ void GBAIOWrite32(struct GBA* gba, uint32_t address, uint32_t value) {
} }
uint16_t GBAIORead(struct GBA* gba, uint32_t address) { uint16_t GBAIORead(struct GBA* gba, uint32_t address) {
gba->lastJump = -1; // IO reads need to invalidate detected idle loops
switch (address) { switch (address) {
case REG_TM0CNT_LO: case REG_TM0CNT_LO:
GBATimerUpdateRegister(gba, 0); GBATimerUpdateRegister(gba, 0);

View File

@ -7,6 +7,7 @@
#include "macros.h" #include "macros.h"
#include "decoder.h"
#include "gba-gpio.h" #include "gba-gpio.h"
#include "gba-io.h" #include "gba-io.h"
#include "gba-serialize.h" #include "gba-serialize.h"
@ -113,16 +114,107 @@ void GBAMemoryReset(struct GBA* gba) {
} }
} }
static void _analyzeForIdleLoop(struct GBA* gba, struct ARMCore* cpu, uint32_t address) {
struct ARMInstructionInfo info;
uint32_t nextAddress = address;
memset(gba->taintedRegisters, 0, sizeof(gba->taintedRegisters));
if (cpu->executionMode == MODE_THUMB) {
while (true) {
uint16_t opcode;
LOAD_16(opcode, nextAddress & cpu->memory.activeMask, cpu->memory.activeRegion);
ARMDecodeThumb(opcode, &info);
switch (info.branchType) {
case ARM_BRANCH_NONE:
if (info.operandFormat & ARM_OPERAND_MEMORY_2) {
if (info.mnemonic == ARM_MN_STR || gba->taintedRegisters[info.memory.baseReg]) {
gba->idleDetectionStep = -1;
return;
}
uint32_t loadAddress = gba->cachedRegisters[info.memory.baseReg];
uint32_t offset = 0;
if (info.memory.format & ARM_MEMORY_IMMEDIATE_OFFSET) {
offset = info.memory.offset.immediate;
} else if (info.memory.format & ARM_MEMORY_REGISTER_OFFSET) {
int reg = info.memory.offset.reg;
if (gba->cachedRegisters[reg]) {
gba->idleDetectionStep = -1;
return;
}
offset = gba->cachedRegisters[reg];
}
if (info.memory.format & ARM_MEMORY_OFFSET_SUBTRACT) {
loadAddress -= offset;
} else {
loadAddress += offset;
}
if ((loadAddress >> BASE_OFFSET) == REGION_IO) {
gba->idleDetectionStep = -1;
return;
}
if ((loadAddress >> BASE_OFFSET) < REGION_CART0 || (loadAddress >> BASE_OFFSET) > REGION_CART2_EX) {
gba->taintedRegisters[info.op1.reg] = true;
} else {
switch (info.memory.width) {
case 1:
gba->cachedRegisters[info.op1.reg] = GBALoad8(cpu, loadAddress, 0);
break;
case 2:
gba->cachedRegisters[info.op1.reg] = GBALoad16(cpu, loadAddress, 0);
break;
case 4:
gba->cachedRegisters[info.op1.reg] = GBALoad32(cpu, loadAddress, 0);
break;
}
}
} else if (info.operandFormat & ARM_OPERAND_AFFECTED_1) {
gba->taintedRegisters[info.op1.reg] = true;
}
nextAddress += WORD_SIZE_THUMB;
break;
case ARM_BRANCH:
if ((uint32_t) info.op1.immediate + nextAddress + WORD_SIZE_THUMB * 2 == address) {
gba->busyLoop = address;
}
gba->idleDetectionStep = -1;
return;
default:
gba->idleDetectionStep = -1;
return;
}
}
} else {
gba->idleDetectionStep = -1;
}
}
static void GBASetActiveRegion(struct ARMCore* cpu, uint32_t address) { static void GBASetActiveRegion(struct ARMCore* cpu, uint32_t address) {
struct GBA* gba = (struct GBA*) cpu->master; struct GBA* gba = (struct GBA*) cpu->master;
struct GBAMemory* memory = &gba->memory; struct GBAMemory* memory = &gba->memory;
if (address == gba->busyLoop && memory->activeRegion != REGION_BIOS) { if (address == gba->lastJump && address == gba->busyLoop && memory->activeRegion != REGION_BIOS) {
GBAHalt(gba); GBAHalt(gba);
} }
int newRegion = address >> BASE_OFFSET; int newRegion = address >> BASE_OFFSET;
if (newRegion == memory->activeRegion) { if (newRegion == memory->activeRegion) {
if (address == gba->lastJump) {
switch (gba->idleDetectionStep) {
case 0:
memcpy(gba->cachedRegisters, cpu->gprs, sizeof(gba->cachedRegisters));
++gba->idleDetectionStep;
break;
case 1:
if (memcmp(gba->cachedRegisters, cpu->gprs, sizeof(gba->cachedRegisters))) {
gba->idleDetectionStep = -1;
break;
}
_analyzeForIdleLoop(gba, cpu, address);
break;
}
} else {
gba->lastJump = address;
gba->idleDetectionStep = 0;
}
return; return;
} }
if (memory->activeRegion == REGION_BIOS) { if (memory->activeRegion == REGION_BIOS) {

View File

@ -119,7 +119,6 @@ struct GBA {
int springIRQ; int springIRQ;
uint32_t biosChecksum; uint32_t biosChecksum;
int* keySource; int* keySource;
uint32_t busyLoop;
struct GBARotationSource* rotationSource; struct GBARotationSource* rotationSource;
struct GBALuminanceSource* luminanceSource; struct GBALuminanceSource* luminanceSource;
struct GBARTCSource* rtcSource; struct GBARTCSource* rtcSource;
@ -135,6 +134,12 @@ struct GBA {
const char* activeFile; const char* activeFile;
int logLevel; int logLevel;
uint32_t busyLoop;
uint32_t lastJump;
int idleDetectionStep;
int32_t cachedRegisters[16];
bool taintedRegisters[16];
}; };
struct GBACartridge { struct GBACartridge {