diff --git a/CHANGES b/CHANGES index f8ef6a01b..44f58dcc8 100644 --- a/CHANGES +++ b/CHANGES @@ -16,6 +16,7 @@ Features: - 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 diff --git a/src/gba/gba-io.c b/src/gba/gba-io.c index 11552f3f5..908e64edf 100644 --- a/src/gba/gba-io.c +++ b/src/gba/gba-io.c @@ -555,6 +555,7 @@ void GBAIOWrite32(struct GBA* gba, uint32_t address, uint32_t value) { } 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); diff --git a/src/gba/gba-memory.c b/src/gba/gba-memory.c index 5de5be05a..853f34904 100644 --- a/src/gba/gba-memory.c +++ b/src/gba/gba-memory.c @@ -7,6 +7,7 @@ #include "macros.h" +#include "decoder.h" #include "gba-gpio.h" #include "gba-io.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) { 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) { diff --git a/src/gba/gba.h b/src/gba/gba.h index a7b13ed3a..6ea19de00 100644 --- a/src/gba/gba.h +++ b/src/gba/gba.h @@ -119,7 +119,6 @@ struct GBA { int springIRQ; uint32_t biosChecksum; int* keySource; - uint32_t busyLoop; struct GBARotationSource* rotationSource; struct GBALuminanceSource* luminanceSource; struct GBARTCSource* rtcSource; @@ -135,6 +134,12 @@ struct GBA { const char* activeFile; int logLevel; + + uint32_t busyLoop; + uint32_t lastJump; + int idleDetectionStep; + int32_t cachedRegisters[16]; + bool taintedRegisters[16]; }; struct GBACartridge {