GBA: Implement idle loop detection
Jeffrey Pfau jeffrey@endrift.com
Sat, 10 Jan 2015 14:20:09 -0800
4 files changed,
101 insertions(+),
2 deletions(-)
M
CHANGES
→
CHANGES
@@ -16,6 +16,7 @@ - Remappable shortcuts for keyboard and gamepad
- Rewinding of emulation - Implemented BIOS routines SoftReset, RegisterRamReset, Diff8bitUnFilterWram, Diff8bitUnFilterVram, and Diff16bitUnFilter - Support IPv6 + - Automatically detect and optimize out idle loops Bugfixes: - Qt: Fix issue with set frame sizes being the wrong height - Qt: Fix emulator crashing when full screen if a game is not running
M
src/gba/gba-io.c
→
src/gba/gba-io.c
@@ -555,6 +555,7 @@ gba->memory.io[(address >> 1) + 1] = value >> 16;
} uint16_t GBAIORead(struct GBA* gba, uint32_t address) { + gba->lastJump = -1; // IO reads need to invalidate detected idle loops switch (address) { case REG_TM0CNT_LO: GBATimerUpdateRegister(gba, 0);
M
src/gba/gba-memory.c
→
src/gba/gba-memory.c
@@ -7,6 +7,7 @@ #include "gba-memory.h"
#include "macros.h" +#include "decoder.h" #include "gba-gpio.h" #include "gba-io.h" #include "gba-serialize.h"@@ -113,16 +114,107 @@ GBALog(gba, GBA_LOG_FATAL, "Could not map memory");
} } +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) { struct GBA* gba = (struct GBA*) cpu->master; 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); } int newRegion = address >> BASE_OFFSET; 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; } if (memory->activeRegion == REGION_BIOS) {
M
src/gba/gba.h
→
src/gba/gba.h
@@ -119,7 +119,6 @@
int springIRQ; uint32_t biosChecksum; int* keySource; - uint32_t busyLoop; struct GBARotationSource* rotationSource; struct GBALuminanceSource* luminanceSource; struct GBARTCSource* rtcSource;@@ -135,6 +134,12 @@
const char* activeFile; int logLevel; + + uint32_t busyLoop; + uint32_t lastJump; + int idleDetectionStep; + int32_t cachedRegisters[16]; + bool taintedRegisters[16]; }; struct GBACartridge {