mirror of https://github.com/mgba-emu/mgba.git
GBA: Implement idle loop detection
This commit is contained in:
parent
293831e3bf
commit
54ef61f715
1
CHANGES
1
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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in New Issue