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
|
- 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
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in New Issue