From 8d833dcc5de1c4a70bfdcaf5578526a37975e364 Mon Sep 17 00:00:00 2001 From: Yanis <35189056+Yanis42@users.noreply.github.com> Date: Thu, 19 Sep 2024 01:36:45 +0200 Subject: [PATCH] Document some CPU functions and add format workflow (#178) * port stuff from oot-vc * build issues * missed one wrong type * review --- .github/workflows/format.yml | 32 +++++ include/emulator/cpu.h | 4 +- src/emulator/cpu.c | 229 ++++++++++++++++++++++++++++++----- src/emulator/system.c | 2 +- 4 files changed, 235 insertions(+), 32 deletions(-) create mode 100644 .github/workflows/format.yml diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml new file mode 100644 index 00000000..a67e8351 --- /dev/null +++ b/.github/workflows/format.yml @@ -0,0 +1,32 @@ +name: Check Format + +on: + push: + pull_request: + +jobs: + format: + runs-on: ubuntu-24.04 + defaults: + run: + shell: bash + + steps: + # Checkout the repository (shallow clone) + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + submodules: recursive + + # Set Git config + - name: Git config + run: git config --global --add safe.directory "$GITHUB_WORKSPACE" + + # Run formatter + - name: Run clang-format + run: ./format + + # Cancel if there's a diff + - name: Check status + run: git diff --name-only --exit-code diff --git a/include/emulator/cpu.h b/include/emulator/cpu.h index 6c85756d..d6e8be21 100644 --- a/include/emulator/cpu.h +++ b/include/emulator/cpu.h @@ -262,7 +262,7 @@ typedef struct CpuOptimize { } CpuOptimize; // size = 0x28 typedef struct Cpu Cpu; -typedef bool (*CpuExecuteFunc)(Cpu* pCPU, s32 nCount, s32 nAddressN64, s32 nAddressGCN); +typedef s32 (*CpuExecuteFunc)(Cpu* pCPU, s32 nCount, s32 nAddressN64, s32 nAddressGCN); // _CPU struct Cpu { @@ -350,7 +350,7 @@ struct Cpu { bool cpuFreeCachedAddress(Cpu* pCPU, s32 nAddress0, s32 nAddress1); bool cpuTestInterrupt(Cpu* pCPU, s32 nMaskIP); bool cpuException(Cpu* pCPU, CpuExceptionCode eCode, s32 nMaskIP); -bool cpuExecute(Cpu* pCPU, u64 nAddressBreak); +bool cpuExecute(Cpu* pCPU, s32 nCount, u64 nAddressBreak); bool cpuSetRegisterCP0(Cpu* pCPU, s32 iRegister, s64 nData); bool cpuGetRegisterCP0(Cpu* pCPU, s32 iRegister, s64* pnData); bool __cpuERET(Cpu* pCPU); diff --git a/src/emulator/cpu.c b/src/emulator/cpu.c index d117a547..d0de5351 100644 --- a/src/emulator/cpu.c +++ b/src/emulator/cpu.c @@ -158,6 +158,12 @@ s32 ganOpcodeLoadFP[] = { 0x8F5B0018, 0x13600013, 0x00000000, 0x8F5B012C, 0x44DBF800, }; +/** + * @brief Mapping of VR4300 to PPC registers. + * + * If bit 0x100 is set the VR4300 register is not directly mapped to any PPC register, + * Instead the register will use the emulated VR4300 object for saving/loading register values. + */ s32 ganMapGPR[] = { 0x0000000A, 0x0000000B, 0x0000000C, 0x0000000E, 0x0000000F, 0x00000010, 0x00000011, 0x00000012, 0x00000013, 0x00000014, 0x00000015, 0x00000016, 0x00000017, 0x00000018, 0x00000019, 0x0000001A, @@ -1781,11 +1787,17 @@ static bool cpuCompile_LWR(Cpu* pCPU, s32* addressGCN) { #ifndef NON_MATCHING #pragma GLOBAL_ASM("asm/non_matchings/cpu/cpuCheckDelaySlot.s") #else -static bool cpuCheckDelaySlot(u32 opcode) { +/** + * @brief Checks the type of delay an instruction has. + * + * @param opcode The instruction to determine the delay type for. + * @return s32 The type of delay the instruction has. + */ +static s32 cpuCheckDelaySlot(u32 opcode) { s32 flag = 0; if (opcode == 0) { - return false; + return 0; } switch (MIPS_OP(opcode)) { @@ -1891,15 +1903,42 @@ static bool cpuCheckDelaySlot(u32 opcode) { } #endif +/** + * @brief Fills a code section of NOPs + * + * @param anCode Pointer to fill nops to. + * @param iCode Position in @code to start filling. + * @param number The amount of NOPs to fill. + */ static inline void cpuCompileNOP(s32* anCode, s32* iCode, s32 number) { while (*iCode != number) { anCode[(*iCode)++] = 0x60000000; } } +/** + * @brief The main MIPS->PPC Dynamic recompiler. + * Largely unfinished. + * + * @param pCPU The emulated VR4300. + * @param pnAddress The address to recompile. + * @param pFunction The function that is being recompiled. + * @param anCode Pointer to the recompiled code. + * @param piCode Pointer to the current position in the recompiled code. + * @param bSlot true if we are recompiling a delay slot. + * @return bool true on success, false otherwise. + */ static bool cpuGetPPC(Cpu* pCPU, s32* pnAddress, CpuFunction* pFunction, s32* anCode, s32* piCode, bool bSlot); #pragma GLOBAL_ASM("asm/non_matchings/cpu/cpuGetPPC.s") +/** + * @brief Creates a new recompiled function block. + * + * @param pCPU The emulated VR4300. + * @param ppFunction A pointer to an already recompiled function, or one that has been created. + * @param nAddressN64 The N64 address of the function to find or create. + * @return bool true on success, false otherwise. + */ bool cpuMakeFunction(Cpu* pCPU, CpuFunction** ppFunction, s32 nAddressN64) { s32 iCode; s32 iCode0; @@ -2028,6 +2067,14 @@ bool cpuMakeFunction(Cpu* pCPU, CpuFunction** ppFunction, s32 nAddressN64) { return true; } +/** + * @brief Searches the recompiled block cache for an address, or creates a new block if one cannot be found. + * + * @param pCPU The emulated VR4300. + * @param nAddressN64 N64 code address to search for. + * @param pnAddressGCN A pointer to set the found PPC code to. + * @return bool true on success, false otherwise. + */ static bool cpuFindAddress(Cpu* pCPU, s32 nAddressN64, s32* pnAddressGCN) { s32 iJump; s32 iCode; @@ -2242,7 +2289,7 @@ static bool cpuExecuteUpdate(Cpu* pCPU, s32* pnAddressGCN, u32 nCount) { // Matches but data doesn't #ifndef NON_MATCHING -static bool cpuExecuteOpcode(Cpu* pCPU, s32 nCount, s32 nAddressN64, s32 nAddressGCN); +static s32 cpuExecuteOpcode(Cpu* pCPU, s32 nCount, s32 nAddressN64, s32 nAddressGCN); #pragma GLOBAL_ASM("asm/non_matchings/cpu/cpuExecuteOpcode.s") #else static inline bool cpuCheckInterrupts(Cpu* pCPU) { @@ -2288,7 +2335,7 @@ static inline bool cpuExecuteCacheInstruction(Cpu* pCPU) { return true; } -static bool cpuExecuteOpcode(Cpu* pCPU, s32 nCount0, s32 nAddressN64, s32 nAddressGCN) { +static s32 cpuExecuteOpcode(Cpu* pCPU, s32 nCount0, s32 nAddressN64, s32 nAddressGCN) { s32 pad1[2]; u64 save; s32 restore; @@ -2631,7 +2678,7 @@ static bool cpuExecuteOpcode(Cpu* pCPU, s32 nCount0, s32 nAddressN64, s32 nAddre pCPU->nWaitPC = (pCPU->nPC & 0xF0000000) | (MIPS_TARGET(nOpcode) << 2); if (pCPU->nWaitPC == pCPU->nPC - 4) { if (!cpuCheckInterrupts(pCPU)) { - return false; + return 0; } } break; @@ -2646,7 +2693,7 @@ static bool cpuExecuteOpcode(Cpu* pCPU, s32 nCount0, s32 nAddressN64, s32 nAddre } if (pCPU->nWaitPC == pCPU->nPC - 4) { if (!cpuCheckInterrupts(pCPU)) { - return false; + return 0; } break; } @@ -3740,7 +3787,7 @@ static bool cpuExecuteOpcode(Cpu* pCPU, s32 nCount0, s32 nAddressN64, s32 nAddre } if (!cpuExecuteUpdate(pCPU, &nAddressGCN, nTick + 1)) { - return false; + return 0; } if (restore) { pCPU->aGPR[31].u64 = save; @@ -3754,14 +3801,14 @@ static bool cpuExecuteOpcode(Cpu* pCPU, s32 nCount0, s32 nAddressN64, s32 nAddre } #endif -static bool cpuExecuteIdle(Cpu* pCPU, s32 nCount, s32 nAddressN64, s32 nAddressGCN) { +static s32 cpuExecuteIdle(Cpu* pCPU, s32 nCount, s32 nAddressN64, s32 nAddressGCN) { Rom* pROM; pROM = SYSTEM_ROM(pCPU->pHost); #if VERSION != MQ_J if (!simulatorTestReset(false, false, false, true)) { - return false; + return 0; } #endif @@ -3779,14 +3826,14 @@ static bool cpuExecuteIdle(Cpu* pCPU, s32 nCount, s32 nAddressN64, s32 nAddressG } if (!cpuExecuteUpdate(pCPU, &nAddressGCN, nCount)) { - return false; + return 0; } pCPU->nTickLast = OSGetTick(); return nAddressGCN; } -static bool cpuExecuteJump(Cpu* pCPU, s32 nCount, s32 nAddressN64, s32 nAddressGCN) { +static s32 cpuExecuteJump(Cpu* pCPU, s32 nCount, s32 nAddressN64, s32 nAddressGCN) { nCount = OSGetTick(); if (pCPU->nWaitPC != 0) { @@ -3803,14 +3850,23 @@ static bool cpuExecuteJump(Cpu* pCPU, s32 nCount, s32 nAddressN64, s32 nAddressG } if (!cpuExecuteUpdate(pCPU, &nAddressGCN, nCount)) { - return false; + return 0; } pCPU->nTickLast = OSGetTick(); return nAddressGCN; } -static bool cpuExecuteCall(Cpu* pCPU, s32 nCount, s32 nAddressN64, s32 nAddressGCN) { +/** + * @brief Executes a call from the dynamic recompiler environment + * + * @param pCPU The emulated VR4300. + * @param nCount Latest tick count + * @param nAddressN64 The N64 address of the call. + * @param nAddressGCN The GameCube address after the call has completed. + * @return s32 The address of the recompiled called function. + */ +static s32 cpuExecuteCall(Cpu* pCPU, s32 nCount, s32 nAddressN64, s32 nAddressGCN) { s32 pad; s32 nReg; s32 count; @@ -3860,18 +3916,18 @@ static bool cpuExecuteCall(Cpu* pCPU, s32 nCount, s32 nAddressN64, s32 nAddressG ICInvalidateRange(anCode, 8); } - // bug: If cpuExecuteUpdate decides to delete the function we're trying to - // call here, our lis/ori will be reverted by treeCallerCheck since we've - // already marked this call site in the callerID for-loop above. The - // reverted lis/ori will store the return N64 address instead of a GCN - // address, so the next time this recompiled call is executed, the CPU will - // jump to that N64 return address in GCN address space and bad things - // happen (usually an invalid instruction or invalid load/store). This is - // known as a "VC crash". - // - // For more details, see https://pastebin.com/V6ANmXt8 + //! @bug: If cpuExecuteUpdate decides to delete the function we're trying to + //! call here, our lis/ori will be reverted by treeCallerCheck since we've + //! already marked this call site in the callerID for-loop above. The + //! reverted lis/ori will store the return N64 address instead of a GCN + //! address, so the next time this recompiled call is executed, the CPU will + //! jump to that N64 return address in GCN address space and bad things + //! happen (usually an invalid instruction or invalid load/store). This is + //! known as a "VC crash". + //! + //! For more details, see https://pastebin.com/V6ANmXt8 if (!cpuExecuteUpdate(pCPU, &nAddressGCN, nCount)) { - return false; + return 0; } nDeltaAddress = (u8*)nAddressGCN - (u8*)&anCode[3]; @@ -3892,10 +3948,19 @@ static bool cpuExecuteCall(Cpu* pCPU, s32 nCount, s32 nAddressN64, s32 nAddressG // Matches but data doesn't #ifndef NON_MATCHING -static bool cpuExecuteLoadStore(Cpu* pCPU, s32 nCount, s32 nAddressN64, s32 nAddressGCN); +static s32 cpuExecuteLoadStore(Cpu* pCPU, s32 nCount, s32 nAddressN64, s32 nAddressGCN); #pragma GLOBAL_ASM("asm/non_matchings/cpu/cpuExecuteLoadStore.s") #else -static bool cpuExecuteLoadStore(Cpu* pCPU, s32 nCount, s32 nAddressN64, s32 nAddressGCN) { +/** + * @brief Recompiles a VR4300 load/store instruction + * + * @param pCPU The emulated VR4300. + * @param nCount Unused. + * @param nAddressN64 The address of the Load/Store instruction. + * @param nAddressGCN A pointer to the location where recompiled code should be stored. + * @return s32 The address of the recompiled called function. + */ +static s32 cpuExecuteLoadStore(Cpu* pCPU, s32 nCount, s32 nAddressN64, s32 nAddressGCN) { u32* opcode; s32 address; s32 iRegisterA; @@ -4169,10 +4234,19 @@ static bool cpuExecuteLoadStore(Cpu* pCPU, s32 nCount, s32 nAddressN64, s32 nAdd // Matches but data doesn't #ifndef NON_MATCHING -static bool cpuExecuteLoadStoreF(Cpu* pCPU, s32 nCount, s32 nAddressN64, s32 nAddressGCN); +static s32 cpuExecuteLoadStoreF(Cpu* pCPU, s32 nCount, s32 nAddressN64, s32 nAddressGCN); #pragma GLOBAL_ASM("asm/non_matchings/cpu/cpuExecuteLoadStoreF.s") #else -static bool cpuExecuteLoadStoreF(Cpu* pCPU, s32 nCount, s32 nAddressN64, s32 nAddressGCN) { +/** + * @brief Recompiles a VR4300 load/store instruction on COP1 or doubleword load/store. + * + * @param pCPU The emulated VR4300. + * @param nCount Unused. + * @param nAddressN64 The address of the Load/Store instruction. + * @param nAddressGCN A pointer to the location where recompiled code should be stored. + * @return s32 The address of the recompiled called function. + */ +static s32 cpuExecuteLoadStoreF(Cpu* pCPU, s32 nCount, s32 nAddressN64, s32 nAddressGCN) { u32* opcode; s32 address; s32 iRegisterA; @@ -4386,6 +4460,16 @@ static bool cpuExecuteLoadStoreF(Cpu* pCPU, s32 nCount, s32 nAddressN64, s32 nAd } #endif +/** + * @brief Generates a call to a virtual-console function from within the dynarec envrionment + * Dedicated PPC registers are saved to the cpu object, and restored once the virtual-console function has finished. + * Jump to the return value of the virtual-console function + * + * @param pCPU The emulated VR4300. + * @param ppfLink A pointer to store the generated PPC code. + * @param pfFunction The virtual-console function to call. + * @return bool true on success, false otherwise. + */ static bool cpuMakeLink(Cpu* pCPU, CpuExecuteFunc* ppfLink, CpuExecuteFunc pfFunction) { s32 iGPR; s32* pnCode; @@ -4447,7 +4531,15 @@ static inline bool cpuFreeLink(Cpu* pCPU, CpuExecuteFunc* ppfLink) { } } -bool cpuExecute(Cpu* pCPU, u64 nAddressBreak) { +/** + * @brief Begins execution of the emulated VR4300 + * + * @param pCPU The emulated VR4300. + * @param nCount Unused. + * @param nAddressBreak Unused. + * @return bool true on success, false otherwise. + */ +bool cpuExecute(Cpu* pCPU, s32 nCount, u64 nAddressBreak) { s32 pad1; s32 iGPR; s32* pnCode; @@ -4886,6 +4978,18 @@ bool cpuException(Cpu* pCPU, CpuExceptionCode eCode, s32 nMaskIP) { return true; } +/** + * @brief Creates a new device and registers memory space for that device. + * + * @param pCPU The emulated VR4300. + * @param piDevice A pointer to the index in the cpu->devices array which the device was created. + * @param pObject The object which will handle reuqests for this device. + * @param nOffset Starting address of the device's address space. + * @param nAddress Starting physical address of the device's address space. + * @param nAddress1 Ending physical address of the device's address space. + * @param nType An argument which will be passed back to the device's event handler. + * @return bool true on success, false otherwise. + */ static bool cpuMakeDevice(Cpu* pCPU, s32* piDevice, void* pObject, s32 nOffset, u32 nAddress0, u32 nAddress1, s32 nType) { CpuDevice* pDevice; @@ -5028,6 +5132,13 @@ static bool cpuSetTLB(Cpu* pCPU, s32 iEntry) { return true; } +/** + * @brief Gets the operating mode of the VR4300 + * + * @param nStatus The status bits to determine the mode for. + * @param peMode A pointer to the mode determined. + * @return bool true on success, false otherwise. + */ static bool cpuGetMode(u64 nStatus, CpuMode* peMode) { if (nStatus & 2) { *peMode = CM_KERNEL; @@ -5055,6 +5166,14 @@ static bool cpuGetMode(u64 nStatus, CpuMode* peMode) { return false; } +/** + * @brief Determines the register size that the VR4300 is using. + * + * @param nStatus Status bits for determining the register size. + * @param peSize A pointer to the size determined. + * @param peMode A pointer to the mode determined. + * @return bool + */ static bool cpuGetSize(u64 nStatus, CpuSize* peSize, CpuMode* peMode) { CpuMode eMode; @@ -5088,6 +5207,14 @@ static bool cpuGetSize(u64 nStatus, CpuSize* peSize, CpuMode* peMode) { return false; } +/** + * @brief Sets the status bits of the VR4300 + * + * @param pCPU The emulated VR4300 + * @param nStatus New status. + * @param unknown Unused. + * @return bool true on success, false otherwise. + */ static bool cpuSetCP0_Status(Cpu* pCPU, u64 nStatus, u32 unknown) { CpuMode eMode; CpuMode eModeLast; @@ -5226,6 +5353,12 @@ bool cpuGetRegisterCP0(Cpu* pCPU, s32 iRegister, s64* pnData) { } #endif +/** + * @brief Sets CP0 values for returnning from an exception. + * + * @param pCPU The emulated VR4300. + * @return bool true on success, false otherwise + */ bool __cpuERET(Cpu* pCPU) { if (pCPU->anCP0[12] & 4) { pCPU->nPC = pCPU->anCP0[30]; @@ -5241,11 +5374,27 @@ bool __cpuERET(Cpu* pCPU) { return true; } +/** + * @brief Sets flags for handling cpu breakpoints. + * + * @param pCPU The emulated VR4300. + * @return bool true on success, false otherwise + */ bool __cpuBreak(Cpu* pCPU) { pCPU->nMode |= 2; return true; } +/** + * @brief Maps an object to a cpu device. + * + * @param pCPU The emulated VR4300. + * @param pObject The device that will handle requests for this memory space. + * @param nAddress0 The start of the memory space for which the device will be responsible. + * @param nAddress1 The end of the memory space for which the device will be responsible. + * @param nType An argument which will be passed back to the device on creation. + * @return bool true on success, false otherwise. + */ bool cpuMapObject(Cpu* pCPU, void* pObject, u32 nAddress0, u32 nAddress1, s32 nType) { s32 iDevice; s32 iAddress; @@ -5292,6 +5441,17 @@ bool cpuMapObject(Cpu* pCPU, void* pObject, u32 nAddress0, u32 nAddress1, s32 nT return true; } +/** + * @brief Sets load handlers for a device. + * + * @param pCPU The emulated VR4300. + * @param pDevice The device which handles the load operations. + * @param pfGet8 byte handler. + * @param pfGet16 halfword handler. + * @param pfGet32 word handler. + * @param pfGet64 doubleword handler. + * @return bool true on success, false otherwise. + */ bool cpuSetDeviceGet(Cpu* pCPU, CpuDevice* pDevice, Get8Func pfGet8, Get16Func pfGet16, Get32Func pfGet32, Get64Func pfGet64) { pDevice->pfGet8 = pfGet8; @@ -5301,6 +5461,17 @@ bool cpuSetDeviceGet(Cpu* pCPU, CpuDevice* pDevice, Get8Func pfGet8, Get16Func p return true; } +/** + * @brief Sets store handlers for a device. + * + * @param pCPU The emulated VR4300. + * @param pDevice The device which handles the store operations. + * @param pfPut8 byte handler. + * @param pfPut16 halfword handler. + * @param pfPut32 word handler. + * @param pfPut64 doubleword handler. + * @return bool true on success, false otherwise. + */ bool cpuSetDevicePut(Cpu* pCPU, CpuDevice* pDevice, Put8Func pfPut8, Put16Func pfPut16, Put32Func pfPut32, Put64Func pfPut64) { pDevice->pfPut8 = pfPut8; diff --git a/src/emulator/system.c b/src/emulator/system.c index 48e1a046..98dd008f 100644 --- a/src/emulator/system.c +++ b/src/emulator/system.c @@ -1864,7 +1864,7 @@ static inline bool systemTestClassObject(System* pSystem) { } bool systemExecute(System* pSystem, s32 nCount) { - if (!cpuExecute(SYSTEM_CPU(pSystem), pSystem->nAddressBreak)) { + if (!cpuExecute(SYSTEM_CPU(pSystem), nCount, pSystem->nAddressBreak)) { if (!systemTestClassObject(pSystem)) { return false; }