mirror of
https://github.com/stenzek/duckstation.git
synced 2026-07-08 17:46:19 +00:00
CPU: Add MIPS assembler
This commit is contained in:
@@ -29,6 +29,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "googletest", "dep\googletes
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "common-tests", "src\common-tests\common-tests.vcxproj", "{EA2B9C7A-B8CC-42F9-879B-191A98680C10}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "core-tests", "src\core-tests\core-tests.vcxproj", "{93D9300C-B64A-4B21-A4B5-30173DE88747}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "scmversion", "src\scmversion\scmversion.vcxproj", "{075CED82-6A20-46DF-94C7-9624AC9DDBEB}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "updater", "src\updater\updater.vcxproj", "{32EEAF44-57F8-4C6C-A6F0-DE5667123DD5}"
|
||||
@@ -523,6 +525,28 @@ Global
|
||||
{EA2B9C7A-B8CC-42F9-879B-191A98680C10}.ReleaseLTCG-Clang|x64.ActiveCfg = ReleaseLTCG-Clang|x64
|
||||
{EA2B9C7A-B8CC-42F9-879B-191A98680C10}.ReleaseLTCG-Clang-SSE2|ARM64.ActiveCfg = ReleaseLTCG-Clang|ARM64
|
||||
{EA2B9C7A-B8CC-42F9-879B-191A98680C10}.ReleaseLTCG-Clang-SSE2|x64.ActiveCfg = ReleaseLTCG-Clang-SSE2|x64
|
||||
{93D9300C-B64A-4B21-A4B5-30173DE88747}.Debug|ARM64.ActiveCfg = Debug-Clang|ARM64
|
||||
{93D9300C-B64A-4B21-A4B5-30173DE88747}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{93D9300C-B64A-4B21-A4B5-30173DE88747}.Debug-Clang|ARM64.ActiveCfg = Debug-Clang|ARM64
|
||||
{93D9300C-B64A-4B21-A4B5-30173DE88747}.Debug-Clang|x64.ActiveCfg = Debug-Clang|x64
|
||||
{93D9300C-B64A-4B21-A4B5-30173DE88747}.Debug-Clang-SSE2|ARM64.ActiveCfg = Debug-Clang|ARM64
|
||||
{93D9300C-B64A-4B21-A4B5-30173DE88747}.Debug-Clang-SSE2|x64.ActiveCfg = Debug-Clang-SSE2|x64
|
||||
{93D9300C-B64A-4B21-A4B5-30173DE88747}.DebugFast|ARM64.ActiveCfg = DebugFast-Clang|ARM64
|
||||
{93D9300C-B64A-4B21-A4B5-30173DE88747}.DebugFast|x64.ActiveCfg = DebugFast|x64
|
||||
{93D9300C-B64A-4B21-A4B5-30173DE88747}.DebugFast-Clang|ARM64.ActiveCfg = DebugFast-Clang|ARM64
|
||||
{93D9300C-B64A-4B21-A4B5-30173DE88747}.DebugFast-Clang|x64.ActiveCfg = DebugFast-Clang|x64
|
||||
{93D9300C-B64A-4B21-A4B5-30173DE88747}.Devel-Clang|ARM64.ActiveCfg = Devel-Clang|ARM64
|
||||
{93D9300C-B64A-4B21-A4B5-30173DE88747}.Devel-Clang|x64.ActiveCfg = Devel-Clang|x64
|
||||
{93D9300C-B64A-4B21-A4B5-30173DE88747}.Release|ARM64.ActiveCfg = Release-Clang|ARM64
|
||||
{93D9300C-B64A-4B21-A4B5-30173DE88747}.Release|x64.ActiveCfg = Release|x64
|
||||
{93D9300C-B64A-4B21-A4B5-30173DE88747}.Release-Clang|ARM64.ActiveCfg = Release-Clang|ARM64
|
||||
{93D9300C-B64A-4B21-A4B5-30173DE88747}.Release-Clang|x64.ActiveCfg = Release-Clang|x64
|
||||
{93D9300C-B64A-4B21-A4B5-30173DE88747}.ReleaseLTCG|ARM64.ActiveCfg = ReleaseLTCG-Clang|ARM64
|
||||
{93D9300C-B64A-4B21-A4B5-30173DE88747}.ReleaseLTCG|x64.ActiveCfg = ReleaseLTCG|x64
|
||||
{93D9300C-B64A-4B21-A4B5-30173DE88747}.ReleaseLTCG-Clang|ARM64.ActiveCfg = ReleaseLTCG-Clang|ARM64
|
||||
{93D9300C-B64A-4B21-A4B5-30173DE88747}.ReleaseLTCG-Clang|x64.ActiveCfg = ReleaseLTCG-Clang|x64
|
||||
{93D9300C-B64A-4B21-A4B5-30173DE88747}.ReleaseLTCG-Clang-SSE2|ARM64.ActiveCfg = ReleaseLTCG-Clang|ARM64
|
||||
{93D9300C-B64A-4B21-A4B5-30173DE88747}.ReleaseLTCG-Clang-SSE2|x64.ActiveCfg = ReleaseLTCG-Clang-SSE2|x64
|
||||
{075CED82-6A20-46DF-94C7-9624AC9DDBEB}.Debug|ARM64.ActiveCfg = Debug-Clang|ARM64
|
||||
{075CED82-6A20-46DF-94C7-9624AC9DDBEB}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{075CED82-6A20-46DF-94C7-9624AC9DDBEB}.Debug|x64.Build.0 = Debug|x64
|
||||
|
||||
@@ -14,5 +14,6 @@ endif()
|
||||
|
||||
if(BUILD_TESTS)
|
||||
add_subdirectory(common-tests EXCLUDE_FROM_ALL)
|
||||
add_subdirectory(core-tests EXCLUDE_FROM_ALL)
|
||||
add_subdirectory(util-tests EXCLUDE_FROM_ALL)
|
||||
endif()
|
||||
|
||||
12
src/core-tests/CMakeLists.txt
Normal file
12
src/core-tests/CMakeLists.txt
Normal file
@@ -0,0 +1,12 @@
|
||||
# SPDX-FileCopyrightText: 2019-2026 Connor McLaughlin <stenzek@gmail.com>
|
||||
# SPDX-License-Identifier: CC-BY-NC-ND-4.0 + Packaging Restriction
|
||||
|
||||
add_executable(core-tests
|
||||
../core/cpu_disasm.cpp
|
||||
../core/cpu_types.cpp
|
||||
cpu_disasm_tests.cpp
|
||||
stub_cpu.cpp
|
||||
)
|
||||
|
||||
target_include_directories(core-tests PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/..")
|
||||
target_link_libraries(core-tests PRIVATE common gtest gtest_main)
|
||||
33
src/core-tests/core-tests.vcxproj
Normal file
33
src/core-tests/core-tests.vcxproj
Normal file
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="..\..\dep\vsprops\Configurations.props" />
|
||||
<ItemGroup>
|
||||
<ClCompile Include="..\..\dep\googletest\src\gtest_main.cc" />
|
||||
<ClCompile Include="..\core\cpu_disasm.cpp" />
|
||||
<ClCompile Include="..\core\cpu_types.cpp" />
|
||||
<ClCompile Include="cpu_disasm_tests.cpp" />
|
||||
<ClCompile Include="stub_cpu.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\dep\googletest\googletest.vcxproj">
|
||||
<Project>{49953e1b-2ef7-46a4-b88b-1bf9e099093b}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\common\common.vcxproj">
|
||||
<Project>{ee054e08-3799-4a59-a422-18259c105ffd}</Project>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>{93D9300C-B64A-4B21-A4B5-30173DE88747}</ProjectGuid>
|
||||
</PropertyGroup>
|
||||
<Import Project="..\..\dep\vsprops\ConsoleApplication.props" />
|
||||
<Import Project="..\common\common.props" />
|
||||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)dep\googletest\include</AdditionalIncludeDirectories>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<Import Project="..\..\dep\vsprops\Targets.props" />
|
||||
</Project>
|
||||
10
src/core-tests/core-tests.vcxproj.filters
Normal file
10
src/core-tests/core-tests.vcxproj.filters
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<ClCompile Include="..\..\dep\googletest\src\gtest_main.cc" />
|
||||
<ClCompile Include="..\core\cpu_disasm.cpp" />
|
||||
<ClCompile Include="..\core\cpu_types.cpp" />
|
||||
<ClCompile Include="cpu_disasm_tests.cpp" />
|
||||
<ClCompile Include="stub_cpu.cpp" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
188
src/core-tests/cpu_disasm_tests.cpp
Normal file
188
src/core-tests/cpu_disasm_tests.cpp
Normal file
@@ -0,0 +1,188 @@
|
||||
// SPDX-FileCopyrightText: 2019-2026 Connor McLaughlin <stenzek@gmail.com>
|
||||
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
|
||||
|
||||
#include "core/cpu_disasm.h"
|
||||
|
||||
#include "common/error.h"
|
||||
#include "common/small_string.h"
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <array>
|
||||
|
||||
namespace {
|
||||
|
||||
static std::string Disassemble(u32 pc, u32 bits)
|
||||
{
|
||||
SmallString result;
|
||||
CPU::DisassembleInstruction(&result, pc, bits);
|
||||
return std::string(result.view());
|
||||
}
|
||||
|
||||
static u32 Assemble(u32 pc, std::string_view text)
|
||||
{
|
||||
u32 result = 0xDEADBEEF;
|
||||
Error error;
|
||||
EXPECT_TRUE(CPU::AssembleInstruction(&result, pc, text, &error)) << error.GetDescription();
|
||||
return result;
|
||||
}
|
||||
|
||||
struct AssemblyCase
|
||||
{
|
||||
std::string_view text;
|
||||
u32 bits;
|
||||
};
|
||||
|
||||
TEST(CPUDisasm, Disassemble)
|
||||
{
|
||||
EXPECT_EQ(Disassemble(0x1000, 0x00000000), "sll zero, zero, 0");
|
||||
EXPECT_EQ(Disassemble(0x1000, 0x012A4020), "add t0, t1, t2");
|
||||
EXPECT_EQ(Disassemble(0x1000, 0x01494007), "srav t0, t1, t2");
|
||||
EXPECT_EQ(Disassemble(0x1000, 0x0109001B), "divu t0, t1");
|
||||
EXPECT_EQ(Disassemble(0x1000, 0x2128FFFC), "addi t0, t1, -4");
|
||||
EXPECT_EQ(Disassemble(0x1000, 0x11000003), "beq t0, zero, 0x00001010");
|
||||
EXPECT_EQ(Disassemble(0x1000, 0x05110003), "bgezal t0, 0x00001010");
|
||||
EXPECT_EQ(Disassemble(0x1000, 0x8FA8FFF0), "lw t0, -0x10(sp)");
|
||||
EXPECT_EQ(Disassemble(0x1000, 0xCBA60010), "lwc2 rgbc, 0x10(sp)");
|
||||
EXPECT_EQ(Disassemble(0x1000, 0x40086000), "mfc0 t0, SR");
|
||||
EXPECT_EQ(Disassemble(0x1000, 0x48481800), "cfc2 t0, rt_3");
|
||||
EXPECT_EQ(Disassemble(0x1000, 0x42000010), "rfe");
|
||||
EXPECT_EQ(Disassemble(0x1000, 0x4A080401), "rtps sf lm");
|
||||
EXPECT_EQ(Disassemble(0x1000, 0x4A000006), "nclip");
|
||||
EXPECT_EQ(Disassemble(0x1000, 0x46123456), "cop1 0x00123456");
|
||||
}
|
||||
|
||||
TEST(CPUDisasm, UnknownInstructions)
|
||||
{
|
||||
EXPECT_EQ(Disassemble(0x1000, 0x60000000), "UNKNOWN");
|
||||
EXPECT_EQ(Disassemble(0x1000, 0x00000001), "UNKNOWN");
|
||||
EXPECT_EQ(Disassemble(0x1000, 0x4A000002), "UNKNOWN");
|
||||
}
|
||||
|
||||
TEST(CPUDisasm, Assemble)
|
||||
{
|
||||
static constexpr std::array cases = {
|
||||
AssemblyCase{"nop", 0x00000000},
|
||||
AssemblyCase{"NOP", 0x00000000},
|
||||
AssemblyCase{"SLL $ZERO, $zero, 0", 0x00000000},
|
||||
AssemblyCase{"srl t0, t1, 4", 0x00094102},
|
||||
AssemblyCase{"sra t0, t1, 4", 0x00094103},
|
||||
AssemblyCase{"sllv t0, t1, t2", 0x01494004},
|
||||
AssemblyCase{"srlv t0, t1, t2", 0x01494006},
|
||||
AssemblyCase{"srav t0, t1, t2", 0x01494007},
|
||||
AssemblyCase{"jr t0", 0x01000008},
|
||||
AssemblyCase{"jalr ra, t0", 0x0100F809},
|
||||
AssemblyCase{"syscall", 0x0000000C},
|
||||
AssemblyCase{"break", 0x0000000D},
|
||||
AssemblyCase{"mfhi t0", 0x00004010},
|
||||
AssemblyCase{"mthi t0", 0x01000011},
|
||||
AssemblyCase{"mflo t0", 0x00004012},
|
||||
AssemblyCase{"mtlo t0", 0x01000013},
|
||||
AssemblyCase{"mult t0, t1", 0x01090018},
|
||||
AssemblyCase{"multu t0, t1", 0x01090019},
|
||||
AssemblyCase{"div t0, t1", 0x0109001A},
|
||||
AssemblyCase{"divu t0, t1", 0x0109001B},
|
||||
AssemblyCase{"add t0, $t1, T2", 0x012A4020},
|
||||
AssemblyCase{"addu t0, t1, t2", 0x012A4021},
|
||||
AssemblyCase{"sub t0, t1, t2", 0x012A4022},
|
||||
AssemblyCase{"subu t0, t1, t2", 0x012A4023},
|
||||
AssemblyCase{"and t0, t1, t2", 0x012A4024},
|
||||
AssemblyCase{"or t0, t1, t2", 0x012A4025},
|
||||
AssemblyCase{"xor t0, t1, t2", 0x012A4026},
|
||||
AssemblyCase{"nor t0, t1, t2", 0x012A4027},
|
||||
AssemblyCase{"slt t0, t1, t2", 0x012A402A},
|
||||
AssemblyCase{"sltu t0, t1, t2", 0x012A402B},
|
||||
AssemblyCase{"j 0x1000", 0x08000400},
|
||||
AssemblyCase{"jal 0x1000", 0x0C000400},
|
||||
AssemblyCase{"beq t1, t2, 0x1010", 0x112A0003},
|
||||
AssemblyCase{"bne t1, t2, 0x1010", 0x152A0003},
|
||||
AssemblyCase{"blez t1, 0x1010", 0x19200003},
|
||||
AssemblyCase{"bgtz t1, 0x1010", 0x1D200003},
|
||||
AssemblyCase{"bltz t0, 0x1010", 0x05000003},
|
||||
AssemblyCase{"bgez t0, 0x1010", 0x05010003},
|
||||
AssemblyCase{"bltzal t0, 0x1010", 0x05100003},
|
||||
AssemblyCase{"bgezal t0, 0x1010", 0x05110003},
|
||||
AssemblyCase{"addi t0,t1,-4", 0x2128FFFC},
|
||||
AssemblyCase{"addiu t0, t1, -4", 0x2528FFFC},
|
||||
AssemblyCase{"slti t0, t1, -4", 0x2928FFFC},
|
||||
AssemblyCase{"sltiu t0, t1, 291", 0x2D280123},
|
||||
AssemblyCase{"andi t0, t1, 0xabcd", 0x3128ABCD},
|
||||
AssemblyCase{"ori t0, t1, 0xabcd", 0x3528ABCD},
|
||||
AssemblyCase{"xori t0, t1, 0xabcd", 0x3928ABCD},
|
||||
AssemblyCase{"lui t0, 0xabcd", 0x3C08ABCD},
|
||||
AssemblyCase{"lb t0, -0x10(sp)", 0x83A8FFF0},
|
||||
AssemblyCase{"lh t0, -0x10(sp)", 0x87A8FFF0},
|
||||
AssemblyCase{"lwl t0, -0x10(sp)", 0x8BA8FFF0},
|
||||
AssemblyCase{"lw t0, -0x10($sp)", 0x8FA8FFF0},
|
||||
AssemblyCase{"lbu t0, -0x10(sp)", 0x93A8FFF0},
|
||||
AssemblyCase{"lhu t0, -0x10(sp)", 0x97A8FFF0},
|
||||
AssemblyCase{"lwr t0, -0x10(sp)", 0x9BA8FFF0},
|
||||
AssemblyCase{"sb t0, -0x10(sp)", 0xA3A8FFF0},
|
||||
AssemblyCase{"sh t0, -0x10(sp)", 0xA7A8FFF0},
|
||||
AssemblyCase{"swl t0, -0x10(sp)", 0xABA8FFF0},
|
||||
AssemblyCase{"sw t0, -0x10(sp)", 0xAFA8FFF0},
|
||||
AssemblyCase{"swr t0, -0x10(sp)", 0xBBA8FFF0},
|
||||
AssemblyCase{"lwc2 rgbc, 0x10(sp)", 0xCBA60010},
|
||||
AssemblyCase{"swc2 rgbc, 0x10(sp)", 0xEBA60010},
|
||||
AssemblyCase{"mfc0 t0, sr", 0x40086000},
|
||||
AssemblyCase{"cfc2 t0, rt_3", 0x48481800},
|
||||
AssemblyCase{"mtc1 t0, 7", 0x44883800},
|
||||
AssemblyCase{"ctc3 t0, 5", 0x4CC82800},
|
||||
AssemblyCase{"rfe", 0x42000010},
|
||||
AssemblyCase{"rtps SF lm", 0x4A080401},
|
||||
AssemblyCase{"nclip", 0x4A000006},
|
||||
AssemblyCase{"mvmva sf lm m=2 v=1 t=3", 0x4A0CE412},
|
||||
AssemblyCase{"cop1 0x123456", 0x46123456},
|
||||
};
|
||||
|
||||
for (const AssemblyCase& test : cases)
|
||||
EXPECT_EQ(Assemble(0x1000, test.text), test.bits) << test.text;
|
||||
}
|
||||
|
||||
TEST(CPUDisasm, AssembleErrorsDoNotModifyDestination)
|
||||
{
|
||||
static constexpr std::array<std::string_view, 14> cases = {
|
||||
"", "unknown t0", "nop t0", "sll t0, t1, 32",
|
||||
"addi t0, t1, 32768", "andi t0, t1, -1", "lw t0, 0", "lw bad, 0(sp)",
|
||||
"beq t0, t1, 3", "beq t0, t1, 0x40000", "j 0x10000000", "mfc0 t0, nope",
|
||||
"mvmva m=0 v=0", "rtps sf sf",
|
||||
};
|
||||
|
||||
for (const std::string_view text : cases)
|
||||
{
|
||||
u32 result = 0xDEADBEEF;
|
||||
Error error;
|
||||
EXPECT_FALSE(CPU::AssembleInstruction(&result, 0x1000, text, &error)) << text;
|
||||
EXPECT_EQ(result, 0xDEADBEEFu) << text;
|
||||
EXPECT_TRUE(error.IsValid()) << text;
|
||||
EXPECT_FALSE(error.GetDescription().empty()) << text;
|
||||
}
|
||||
}
|
||||
|
||||
TEST(CPUDisasm, RoundTrip)
|
||||
{
|
||||
static constexpr std::array<u32, 54> instructions = {
|
||||
0x00000000, 0x00094102, 0x00094103, 0x01494004, 0x01494006, 0x01494007,
|
||||
0x01000008, 0x0100F809, 0x0000000C, 0x0000000D, 0x00004010, 0x01000011,
|
||||
0x00004012, 0x01000013, 0x01090018, 0x01090019, 0x0109001A, 0x0109001B,
|
||||
0x012A4020, 0x012A4021, 0x012A4022, 0x012A4023, 0x012A4024, 0x012A4025,
|
||||
0x012A4026, 0x012A4027, 0x012A402A, 0x012A402B, 0x08000400, 0x0C000400,
|
||||
0x112A0003, 0x152A0003, 0x19200003, 0x1D200003, 0x05000003, 0x05010003,
|
||||
0x05100003, 0x05110003, 0x2128FFFC, 0x2528FFFC, 0x2928FFFC, 0x2D280123,
|
||||
0x3128ABCD, 0x3528ABCD, 0x3928ABCD, 0x3C08ABCD, 0x83A8FFF0, 0x8FA8FFF0,
|
||||
0xBBA8FFF0, 0xCBA60010, 0xEBA60010, 0x40086000, 0x4A0CE412, 0x46123456,
|
||||
};
|
||||
|
||||
for (const u32 bits : instructions)
|
||||
{
|
||||
const std::string text = Disassemble(0x1000, bits);
|
||||
u32 assembled = 0;
|
||||
Error error;
|
||||
ASSERT_TRUE(CPU::AssembleInstruction(&assembled, 0x1000, text, &error))
|
||||
<< fmt::format("0x{:08x}: {}: {}", bits, text, error.GetDescription());
|
||||
EXPECT_EQ(assembled, bits) << text;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
27
src/core-tests/stub_cpu.cpp
Normal file
27
src/core-tests/stub_cpu.cpp
Normal file
@@ -0,0 +1,27 @@
|
||||
// SPDX-FileCopyrightText: 2019-2026 Connor McLaughlin <stenzek@gmail.com>
|
||||
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
|
||||
|
||||
#include "core/cpu_core.h"
|
||||
|
||||
CPU::State CPU::g_state;
|
||||
|
||||
bool CPU::SafeReadMemoryByte(VirtualMemoryAddress addr, u8* value)
|
||||
{
|
||||
(void)addr;
|
||||
*value = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CPU::SafeReadMemoryHalfWord(VirtualMemoryAddress addr, u16* value)
|
||||
{
|
||||
(void)addr;
|
||||
*value = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CPU::SafeReadMemoryWord(VirtualMemoryAddress addr, u32* value)
|
||||
{
|
||||
(void)addr;
|
||||
*value = 0;
|
||||
return false;
|
||||
}
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
#include <array>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
|
||||
@@ -6,30 +6,21 @@
|
||||
#include "cpu_types.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/error.h"
|
||||
#include "common/small_string.h"
|
||||
#include "common/string_util.h"
|
||||
|
||||
#include <array>
|
||||
#include <limits>
|
||||
|
||||
namespace CPU {
|
||||
namespace {
|
||||
|
||||
enum Operand : u8
|
||||
enum class AssembleResult
|
||||
{
|
||||
Operand_None,
|
||||
i_rs,
|
||||
i_rt,
|
||||
i_imm,
|
||||
j_target,
|
||||
r_rs,
|
||||
r_rt,
|
||||
r_rd,
|
||||
r_shamt,
|
||||
r_funct
|
||||
};
|
||||
|
||||
struct TableEntry
|
||||
{
|
||||
const char* format;
|
||||
NoMatch,
|
||||
Success,
|
||||
Error,
|
||||
};
|
||||
|
||||
struct GTEInstructionTable
|
||||
@@ -54,9 +45,24 @@ static void FormatCopComment(SmallStringBase* dest, u32 pc, const Instruction in
|
||||
|
||||
static void FormatGTEInstruction(SmallStringBase* dest, u32 pc, const Instruction inst);
|
||||
|
||||
static std::string_view GetMnemonic(std::string_view text);
|
||||
static std::string_view GetOperands(std::string_view text);
|
||||
static std::string GetFormatMnemonic(const char* format, u32 cop_n);
|
||||
static bool SplitOperands(std::string_view text, std::array<std::string_view, 4>* operands, size_t* count);
|
||||
static bool ParseSignedValue(std::string_view text, s64* value);
|
||||
static bool ParseUnsignedValue(std::string_view text, u64* value);
|
||||
static bool ParseGPR(std::string_view text, u32* index);
|
||||
static bool ParseNumericRegister(std::string_view text, u32* index);
|
||||
static bool ParseCopRegister(std::string_view text, u32 cop_n, bool control, u32* index);
|
||||
static bool SetField(u32* bits, u32 shift, u32 width, u32 value);
|
||||
static bool AssembleFormat(u32* bits, u32 pc, const char* format, std::string_view text, Error* error);
|
||||
static AssembleResult TryTableEntry(u32* dest, u32 pc, std::string_view text, const char* format, u32 base_bits,
|
||||
Error* error);
|
||||
static AssembleResult AssembleGTEInstruction(u32* dest, std::string_view text, Error* error);
|
||||
|
||||
static const std::array<const char*, 64> s_base_table = {{
|
||||
"", // 0
|
||||
"UNKNOWN", // 1
|
||||
nullptr, // 1
|
||||
"j $jt", // 2
|
||||
"jal $jt", // 3
|
||||
"beq $rs, $rt, $rel", // 4
|
||||
@@ -71,22 +77,22 @@ static const std::array<const char*, 64> s_base_table = {{
|
||||
"ori $rt, $rs, $immx", // 13
|
||||
"xori $rt, $rs, $immx", // 14
|
||||
"lui $rt, $immx", // 15
|
||||
"UNKNOWN", // 16
|
||||
"UNKNOWN", // 17
|
||||
"UNKNOWN", // 18
|
||||
"UNKNOWN", // 19
|
||||
"UNKNOWN", // 20
|
||||
"UNKNOWN", // 21
|
||||
"UNKNOWN", // 22
|
||||
"UNKNOWN", // 23
|
||||
"UNKNOWN", // 24
|
||||
"UNKNOWN", // 25
|
||||
"UNKNOWN", // 26
|
||||
"UNKNOWN", // 27
|
||||
"UNKNOWN", // 28
|
||||
"UNKNOWN", // 29
|
||||
"UNKNOWN", // 30
|
||||
"UNKNOWN", // 31
|
||||
nullptr, // 16
|
||||
nullptr, // 17
|
||||
nullptr, // 18
|
||||
nullptr, // 19
|
||||
nullptr, // 20
|
||||
nullptr, // 21
|
||||
nullptr, // 22
|
||||
nullptr, // 23
|
||||
nullptr, // 24
|
||||
nullptr, // 25
|
||||
nullptr, // 26
|
||||
nullptr, // 27
|
||||
nullptr, // 28
|
||||
nullptr, // 29
|
||||
nullptr, // 30
|
||||
nullptr, // 31
|
||||
"lb $rt, $offsetrs", // 32
|
||||
"lh $rt, $offsetrs", // 33
|
||||
"lwl $rt, $offsetrs", // 34
|
||||
@@ -94,66 +100,66 @@ static const std::array<const char*, 64> s_base_table = {{
|
||||
"lbu $rt, $offsetrs", // 36
|
||||
"lhu $rt, $offsetrs", // 37
|
||||
"lwr $rt, $offsetrs", // 38
|
||||
"UNKNOWN", // 39
|
||||
nullptr, // 39
|
||||
"sb $rt, $offsetrs", // 40
|
||||
"sh $rt, $offsetrs", // 41
|
||||
"swl $rt, $offsetrs", // 42
|
||||
"sw $rt, $offsetrs", // 43
|
||||
"UNKNOWN", // 44
|
||||
"UNKNOWN", // 45
|
||||
nullptr, // 44
|
||||
nullptr, // 45
|
||||
"swr $rt, $offsetrs", // 46
|
||||
"UNKNOWN", // 47
|
||||
nullptr, // 47
|
||||
"lwc0 $coprt, $offsetrs", // 48
|
||||
"lwc1 $coprt, $offsetrs", // 49
|
||||
"lwc2 $coprt, $offsetrs", // 50
|
||||
"lwc3 $coprt, $offsetrs", // 51
|
||||
"UNKNOWN", // 52
|
||||
"UNKNOWN", // 53
|
||||
"UNKNOWN", // 54
|
||||
"UNKNOWN", // 55
|
||||
nullptr, // 52
|
||||
nullptr, // 53
|
||||
nullptr, // 54
|
||||
nullptr, // 55
|
||||
"swc0 $coprt, $offsetrs", // 56
|
||||
"swc1 $coprt, $offsetrs", // 57
|
||||
"swc2 $coprt, $offsetrs", // 58
|
||||
"swc3 $coprt, $offsetrs", // 59
|
||||
"UNKNOWN", // 60
|
||||
"UNKNOWN", // 61
|
||||
"UNKNOWN", // 62
|
||||
"UNKNOWN" // 63
|
||||
nullptr, // 60
|
||||
nullptr, // 61
|
||||
nullptr, // 62
|
||||
nullptr // 63
|
||||
}};
|
||||
|
||||
static const std::array<const char*, 64> s_special_table = {{
|
||||
"sll $rd, $rt, $shamt", // 0
|
||||
"UNKNOWN", // 1
|
||||
nullptr, // 1
|
||||
"srl $rd, $rt, $shamt", // 2
|
||||
"sra $rd, $rt, $shamt", // 3
|
||||
"sllv $rd, $rt, $rs", // 4
|
||||
"UNKNOWN", // 5
|
||||
nullptr, // 5
|
||||
"srlv $rd, $rt, $rs", // 6
|
||||
"srav $rd, $rt, $rs", // 7
|
||||
"jr $rs", // 8
|
||||
"jalr $rd, $rs", // 9
|
||||
"UNKNOWN", // 10
|
||||
"UNKNOWN", // 11
|
||||
nullptr, // 10
|
||||
nullptr, // 11
|
||||
"syscall", // 12
|
||||
"break", // 13
|
||||
"UNKNOWN", // 14
|
||||
"UNKNOWN", // 15
|
||||
nullptr, // 14
|
||||
nullptr, // 15
|
||||
"mfhi $rd", // 16
|
||||
"mthi $rs", // 17
|
||||
"mflo $rd", // 18
|
||||
"mtlo $rs", // 19
|
||||
"UNKNOWN", // 20
|
||||
"UNKNOWN", // 21
|
||||
"UNKNOWN", // 22
|
||||
"UNKNOWN", // 23
|
||||
nullptr, // 20
|
||||
nullptr, // 21
|
||||
nullptr, // 22
|
||||
nullptr, // 23
|
||||
"mult $rs, $rt", // 24
|
||||
"multu $rs, $rt", // 25
|
||||
"div $rs, $rt", // 26
|
||||
"divu $rs, $rt", // 27
|
||||
"UNKNOWN", // 28
|
||||
"UNKNOWN", // 29
|
||||
"UNKNOWN", // 30
|
||||
"UNKNOWN", // 31
|
||||
nullptr, // 28
|
||||
nullptr, // 29
|
||||
nullptr, // 30
|
||||
nullptr, // 31
|
||||
"add $rd, $rs, $rt", // 32
|
||||
"addu $rd, $rs, $rt", // 33
|
||||
"sub $rd, $rs, $rt", // 34
|
||||
@@ -162,30 +168,30 @@ static const std::array<const char*, 64> s_special_table = {{
|
||||
"or $rd, $rs, $rt", // 37
|
||||
"xor $rd, $rs, $rt", // 38
|
||||
"nor $rd, $rs, $rt", // 39
|
||||
"UNKNOWN", // 40
|
||||
"UNKNOWN", // 41
|
||||
nullptr, // 40
|
||||
nullptr, // 41
|
||||
"slt $rd, $rs, $rt", // 42
|
||||
"sltu $rd, $rs, $rt", // 43
|
||||
"UNKNOWN", // 44
|
||||
"UNKNOWN", // 45
|
||||
"UNKNOWN", // 46
|
||||
"UNKNOWN", // 47
|
||||
"UNKNOWN", // 48
|
||||
"UNKNOWN", // 49
|
||||
"UNKNOWN", // 50
|
||||
"UNKNOWN", // 51
|
||||
"UNKNOWN", // 52
|
||||
"UNKNOWN", // 53
|
||||
"UNKNOWN", // 54
|
||||
"UNKNOWN", // 55
|
||||
"UNKNOWN", // 56
|
||||
"UNKNOWN", // 57
|
||||
"UNKNOWN", // 58
|
||||
"UNKNOWN", // 59
|
||||
"UNKNOWN", // 60
|
||||
"UNKNOWN", // 61
|
||||
"UNKNOWN", // 62
|
||||
"UNKNOWN" // 63
|
||||
nullptr, // 44
|
||||
nullptr, // 45
|
||||
nullptr, // 46
|
||||
nullptr, // 47
|
||||
nullptr, // 48
|
||||
nullptr, // 49
|
||||
nullptr, // 50
|
||||
nullptr, // 51
|
||||
nullptr, // 52
|
||||
nullptr, // 53
|
||||
nullptr, // 54
|
||||
nullptr, // 55
|
||||
nullptr, // 56
|
||||
nullptr, // 57
|
||||
nullptr, // 58
|
||||
nullptr, // 59
|
||||
nullptr, // 60
|
||||
nullptr, // 61
|
||||
nullptr, // 62
|
||||
nullptr // 63
|
||||
}};
|
||||
|
||||
static const std::array<std::pair<CopCommonInstruction, const char*>, 4> s_cop_common_table = {
|
||||
@@ -209,70 +215,70 @@ static constexpr const std::array<const char*, 32> s_cop0_register_names = {
|
||||
"$22", "$23", "$24", "$25", "$26", "$27", "$28", "$29", "$30", "$31"}};
|
||||
|
||||
static constexpr const std::array<GTEInstructionTable, 64> s_gte_instructions = {{
|
||||
{"UNKNOWN", false, false, false}, // 0x00
|
||||
{"rtps", true, true, false}, // 0x01
|
||||
{"UNKNOWN", false, false, false}, // 0x02
|
||||
{"UNKNOWN", false, false, false}, // 0x03
|
||||
{"UNKNOWN", false, false, false}, // 0x04
|
||||
{"UNKNOWN", false, false, false}, // 0x05
|
||||
{"nclip", false, false, false}, // 0x06
|
||||
{"UNKNOWN", false, false, false}, // 0x07
|
||||
{"UNKNOWN", false, false, false}, // 0x08
|
||||
{"UNKNOWN", false, false, false}, // 0x09
|
||||
{"UNKNOWN", false, false, false}, // 0x0A
|
||||
{"UNKNOWN", false, false, false}, // 0x0B
|
||||
{"op", true, true, false}, // 0x0C
|
||||
{"UNKNOWN", false, false, false}, // 0x0D
|
||||
{"UNKNOWN", false, false, false}, // 0x0E
|
||||
{"UNKNOWN", false, false, false}, // 0x0F
|
||||
{"dpcs", true, true, false}, // 0x10
|
||||
{"intpl", true, true, false}, // 0x11
|
||||
{"mvmva", true, true, true}, // 0x12
|
||||
{"ncds", true, true, false}, // 0x13
|
||||
{"cdp", true, true, false}, // 0x14
|
||||
{"UNKNOWN", false, false, false}, // 0x15
|
||||
{"ncdt", true, true, false}, // 0x16
|
||||
{"UNKNOWN", false, false, false}, // 0x17
|
||||
{"UNKNOWN", false, false, false}, // 0x18
|
||||
{"UNKNOWN", false, false, false}, // 0x19
|
||||
{"UNKNOWN", false, false, false}, // 0x1A
|
||||
{"nccs", true, true, false}, // 0x1B
|
||||
{"cc", true, true, false}, // 0x1C
|
||||
{"UNKNOWN", false, false, false}, // 0x1D
|
||||
{"ncs", true, true, false}, // 0x1E
|
||||
{"UNKNOWN", false, false, false}, // 0x1F
|
||||
{"nct", true, true, false}, // 0x20
|
||||
{"UNKNOWN", false, false, false}, // 0x21
|
||||
{"UNKNOWN", false, false, false}, // 0x22
|
||||
{"UNKNOWN", false, false, false}, // 0x23
|
||||
{"UNKNOWN", false, false, false}, // 0x24
|
||||
{"UNKNOWN", false, false, false}, // 0x25
|
||||
{"UNKNOWN", false, false, false}, // 0x26
|
||||
{"UNKNOWN", false, false, false}, // 0x27
|
||||
{"sqr", true, true, false}, // 0x28
|
||||
{"dcpl", true, true, false}, // 0x29
|
||||
{"dpct", true, true, false}, // 0x2A
|
||||
{"UNKNOWN", false, false, false}, // 0x2B
|
||||
{"UNKNOWN", false, false, false}, // 0x2C
|
||||
{"avsz3", false, false, false}, // 0x2D
|
||||
{"avsz4", false, false, false}, // 0x2E
|
||||
{"UNKNOWN", false, false, false}, // 0x2F
|
||||
{"rtpt", true, true, false}, // 0x30
|
||||
{"UNKNOWN", false, false, false}, // 0x31
|
||||
{"UNKNOWN", false, false, false}, // 0x32
|
||||
{"UNKNOWN", false, false, false}, // 0x33
|
||||
{"UNKNOWN", false, false, false}, // 0x34
|
||||
{"UNKNOWN", false, false, false}, // 0x35
|
||||
{"UNKNOWN", false, false, false}, // 0x36
|
||||
{"UNKNOWN", false, false, false}, // 0x37
|
||||
{"UNKNOWN", false, false, false}, // 0x38
|
||||
{"UNKNOWN", false, false, false}, // 0x39
|
||||
{"UNKNOWN", false, false, false}, // 0x3A
|
||||
{"UNKNOWN", false, false, false}, // 0x3B
|
||||
{"UNKNOWN", false, false, false}, // 0x3C
|
||||
{"gpf", true, true, false}, // 0x3D
|
||||
{"gpl", true, true, false}, // 0x3E
|
||||
{"ncct", true, true, false}, // 0x3F
|
||||
{nullptr, false, false, false}, // 0x00
|
||||
{"rtps", true, true, false}, // 0x01
|
||||
{nullptr, false, false, false}, // 0x02
|
||||
{nullptr, false, false, false}, // 0x03
|
||||
{nullptr, false, false, false}, // 0x04
|
||||
{nullptr, false, false, false}, // 0x05
|
||||
{"nclip", false, false, false}, // 0x06
|
||||
{nullptr, false, false, false}, // 0x07
|
||||
{nullptr, false, false, false}, // 0x08
|
||||
{nullptr, false, false, false}, // 0x09
|
||||
{nullptr, false, false, false}, // 0x0A
|
||||
{nullptr, false, false, false}, // 0x0B
|
||||
{"op", true, true, false}, // 0x0C
|
||||
{nullptr, false, false, false}, // 0x0D
|
||||
{nullptr, false, false, false}, // 0x0E
|
||||
{nullptr, false, false, false}, // 0x0F
|
||||
{"dpcs", true, true, false}, // 0x10
|
||||
{"intpl", true, true, false}, // 0x11
|
||||
{"mvmva", true, true, true}, // 0x12
|
||||
{"ncds", true, true, false}, // 0x13
|
||||
{"cdp", true, true, false}, // 0x14
|
||||
{nullptr, false, false, false}, // 0x15
|
||||
{"ncdt", true, true, false}, // 0x16
|
||||
{nullptr, false, false, false}, // 0x17
|
||||
{nullptr, false, false, false}, // 0x18
|
||||
{nullptr, false, false, false}, // 0x19
|
||||
{nullptr, false, false, false}, // 0x1A
|
||||
{"nccs", true, true, false}, // 0x1B
|
||||
{"cc", true, true, false}, // 0x1C
|
||||
{nullptr, false, false, false}, // 0x1D
|
||||
{"ncs", true, true, false}, // 0x1E
|
||||
{nullptr, false, false, false}, // 0x1F
|
||||
{"nct", true, true, false}, // 0x20
|
||||
{nullptr, false, false, false}, // 0x21
|
||||
{nullptr, false, false, false}, // 0x22
|
||||
{nullptr, false, false, false}, // 0x23
|
||||
{nullptr, false, false, false}, // 0x24
|
||||
{nullptr, false, false, false}, // 0x25
|
||||
{nullptr, false, false, false}, // 0x26
|
||||
{nullptr, false, false, false}, // 0x27
|
||||
{"sqr", true, true, false}, // 0x28
|
||||
{"dcpl", true, true, false}, // 0x29
|
||||
{"dpct", true, true, false}, // 0x2A
|
||||
{nullptr, false, false, false}, // 0x2B
|
||||
{nullptr, false, false, false}, // 0x2C
|
||||
{"avsz3", false, false, false}, // 0x2D
|
||||
{"avsz4", false, false, false}, // 0x2E
|
||||
{nullptr, false, false, false}, // 0x2F
|
||||
{"rtpt", true, true, false}, // 0x30
|
||||
{nullptr, false, false, false}, // 0x31
|
||||
{nullptr, false, false, false}, // 0x32
|
||||
{nullptr, false, false, false}, // 0x33
|
||||
{nullptr, false, false, false}, // 0x34
|
||||
{nullptr, false, false, false}, // 0x35
|
||||
{nullptr, false, false, false}, // 0x36
|
||||
{nullptr, false, false, false}, // 0x37
|
||||
{nullptr, false, false, false}, // 0x38
|
||||
{nullptr, false, false, false}, // 0x39
|
||||
{nullptr, false, false, false}, // 0x3A
|
||||
{nullptr, false, false, false}, // 0x3B
|
||||
{nullptr, false, false, false}, // 0x3C
|
||||
{"gpf", true, true, false}, // 0x3D
|
||||
{"gpl", true, true, false}, // 0x3E
|
||||
{"ncct", true, true, false}, // 0x3F
|
||||
}};
|
||||
|
||||
} // namespace CPU
|
||||
@@ -281,6 +287,12 @@ void CPU::FormatInstruction(SmallStringBase* dest, const Instruction inst, u32 p
|
||||
{
|
||||
dest->clear();
|
||||
|
||||
if (!format)
|
||||
{
|
||||
dest->assign("UNKNOWN");
|
||||
return;
|
||||
}
|
||||
|
||||
const char* str = format;
|
||||
while (*str != '\0')
|
||||
{
|
||||
@@ -397,6 +409,9 @@ void CPU::FormatInstruction(SmallStringBase* dest, const Instruction inst, u32 p
|
||||
|
||||
void CPU::FormatComment(SmallStringBase* dest, const Instruction inst, u32 pc, const char* format)
|
||||
{
|
||||
if (!format)
|
||||
return;
|
||||
|
||||
const CPU::Registers* regs = &CPU::g_state.regs;
|
||||
|
||||
const char* str = format;
|
||||
@@ -599,7 +614,7 @@ void CPU::FormatCopInstruction(SmallStringBase* dest, u32 pc, const Instruction
|
||||
}
|
||||
}
|
||||
|
||||
dest->format("<cop{} 0x{:08x}>", ZeroExtend32(inst.cop.cop_n.GetValue()), inst.cop.imm25.GetValue());
|
||||
dest->format("cop{} 0x{:08x}", ZeroExtend32(inst.cop.cop_n.GetValue()), inst.cop.imm25.GetValue());
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
@@ -620,6 +635,12 @@ void CPU::FormatGTEInstruction(SmallStringBase* dest, u32 pc, const Instruction
|
||||
{
|
||||
const GTE::Instruction gi{inst.bits};
|
||||
const GTEInstructionTable& t = s_gte_instructions[gi.command];
|
||||
if (!t.name)
|
||||
{
|
||||
dest->assign("UNKNOWN");
|
||||
return;
|
||||
}
|
||||
|
||||
dest->assign(t.name);
|
||||
|
||||
if (t.sf && gi.sf)
|
||||
@@ -673,7 +694,7 @@ void CPU::DisassembleInstruction(SmallStringBase* dest, u32 pc, u32 bits)
|
||||
case InstructionOp::cop3:
|
||||
default:
|
||||
{
|
||||
dest->format("<cop{} 0x{:08x}>", ZeroExtend32(inst.cop.cop_n.GetValue()), inst.cop.imm25.GetValue());
|
||||
dest->format("cop{} 0x{:08x}", ZeroExtend32(inst.cop.cop_n.GetValue()), inst.cop.imm25.GetValue());
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -736,7 +757,7 @@ void CPU::DisassembleInstructionComment(SmallStringBase* dest, u32 pc, u32 bits)
|
||||
case InstructionOp::cop3:
|
||||
default:
|
||||
{
|
||||
dest->format("<cop{} 0x{:08x}>", ZeroExtend32(inst.cop.cop_n.GetValue()), inst.cop.imm25.GetValue());
|
||||
dest->format("cop{} 0x{:08x}", ZeroExtend32(inst.cop.cop_n.GetValue()), inst.cop.imm25.GetValue());
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -772,3 +793,532 @@ const char* CPU::GetCop0RegisterName(u32 index)
|
||||
{
|
||||
return (index < s_cop0_register_names.size()) ? s_cop0_register_names[index] : "";
|
||||
}
|
||||
|
||||
std::string_view CPU::GetMnemonic(std::string_view text)
|
||||
{
|
||||
const size_t end = text.find_first_of(" \t\r\n");
|
||||
return text.substr(0, end);
|
||||
}
|
||||
|
||||
std::string_view CPU::GetOperands(std::string_view text)
|
||||
{
|
||||
const size_t end = text.find_first_of(" \t\r\n");
|
||||
return (end == std::string_view::npos) ? std::string_view() : StringUtil::StripWhitespace(text.substr(end));
|
||||
}
|
||||
|
||||
std::string CPU::GetFormatMnemonic(const char* format, u32 cop_n)
|
||||
{
|
||||
std::string mnemonic;
|
||||
for (const char* ptr = format; *ptr && !StringUtil::IsWhitespace(*ptr);)
|
||||
{
|
||||
if (std::strncmp(ptr, "$cop", 4) == 0)
|
||||
{
|
||||
mnemonic.push_back(static_cast<char>('0' + cop_n));
|
||||
ptr += 4;
|
||||
}
|
||||
else
|
||||
{
|
||||
mnemonic.push_back(*ptr++);
|
||||
}
|
||||
}
|
||||
return mnemonic;
|
||||
}
|
||||
|
||||
bool CPU::SplitOperands(std::string_view text, std::array<std::string_view, 4>* operands, size_t* count)
|
||||
{
|
||||
*count = 0;
|
||||
text = StringUtil::StripWhitespace(text);
|
||||
if (text.empty())
|
||||
return true;
|
||||
|
||||
size_t start = 0;
|
||||
u32 parentheses = 0;
|
||||
for (size_t i = 0; i <= text.size(); i++)
|
||||
{
|
||||
const char ch = (i < text.size()) ? text[i] : ',';
|
||||
if (ch == '(')
|
||||
parentheses++;
|
||||
else if (ch == ')')
|
||||
{
|
||||
if (parentheses == 0)
|
||||
return false;
|
||||
parentheses--;
|
||||
}
|
||||
else if (ch == ',' && parentheses == 0)
|
||||
{
|
||||
if (*count == operands->size())
|
||||
return false;
|
||||
|
||||
const std::string_view operand = StringUtil::StripWhitespace(text.substr(start, i - start));
|
||||
if (operand.empty())
|
||||
return false;
|
||||
(*operands)[(*count)++] = operand;
|
||||
start = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return (parentheses == 0);
|
||||
}
|
||||
|
||||
bool CPU::ParseSignedValue(std::string_view text, s64* value)
|
||||
{
|
||||
text = StringUtil::StripWhitespace(text);
|
||||
const bool negative = (!text.empty() && text.front() == '-');
|
||||
if (negative || (!text.empty() && text.front() == '+'))
|
||||
text.remove_prefix(1);
|
||||
if (text.empty())
|
||||
return false;
|
||||
|
||||
std::string_view end;
|
||||
const std::optional<u64> parsed = StringUtil::FromCharsWithOptionalBase<u64>(text, &end);
|
||||
static constexpr u64 negative_limit = static_cast<u64>(std::numeric_limits<s64>::max()) + 1;
|
||||
if (!parsed.has_value() || !end.empty() ||
|
||||
parsed.value() > (negative ? negative_limit : static_cast<u64>(std::numeric_limits<s64>::max())))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (negative)
|
||||
{
|
||||
*value = (parsed.value() == negative_limit) ? std::numeric_limits<s64>::min() : -static_cast<s64>(parsed.value());
|
||||
}
|
||||
else
|
||||
{
|
||||
*value = static_cast<s64>(parsed.value());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CPU::ParseUnsignedValue(std::string_view text, u64* value)
|
||||
{
|
||||
text = StringUtil::StripWhitespace(text);
|
||||
std::string_view end;
|
||||
const std::optional<u64> parsed = StringUtil::FromCharsWithOptionalBase<u64>(text, &end);
|
||||
if (!parsed.has_value() || !end.empty())
|
||||
return false;
|
||||
*value = parsed.value();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CPU::ParseGPR(std::string_view text, u32* index)
|
||||
{
|
||||
text = StringUtil::StripWhitespace(text);
|
||||
if (!text.empty() && text.front() == '$')
|
||||
text.remove_prefix(1);
|
||||
|
||||
for (u32 i = 0; i < static_cast<u32>(Reg::count); i++)
|
||||
{
|
||||
if (StringUtil::EqualNoCase(text, GetRegName(static_cast<Reg>(i))))
|
||||
{
|
||||
*index = i;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CPU::ParseNumericRegister(std::string_view text, u32* index)
|
||||
{
|
||||
text = StringUtil::StripWhitespace(text);
|
||||
if (!text.empty() && text.front() == '$')
|
||||
text.remove_prefix(1);
|
||||
|
||||
u64 value;
|
||||
if (!ParseUnsignedValue(text, &value) || value >= 32)
|
||||
return false;
|
||||
*index = static_cast<u32>(value);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CPU::ParseCopRegister(std::string_view text, u32 cop_n, bool control, u32* index)
|
||||
{
|
||||
if (cop_n == 2)
|
||||
{
|
||||
const u32 start = control ? 32 : 0;
|
||||
for (u32 i = 0; i < 32; i++)
|
||||
{
|
||||
if (StringUtil::EqualNoCase(text, s_gte_register_names[start + i]))
|
||||
{
|
||||
*index = i;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (cop_n == 0 && !control)
|
||||
{
|
||||
for (u32 i = 0; i < s_cop0_register_names.size(); i++)
|
||||
{
|
||||
if (StringUtil::EqualNoCase(text, s_cop0_register_names[i]))
|
||||
{
|
||||
*index = i;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ParseNumericRegister(text, index);
|
||||
}
|
||||
|
||||
bool CPU::SetField(u32* bits, u32 shift, u32 width, u32 value)
|
||||
{
|
||||
const u32 mask = ((UINT32_C(1) << width) - 1) << shift;
|
||||
*bits = (*bits & ~mask) | (value << shift);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CPU::AssembleFormat(u32* bits, u32 pc, const char* format, std::string_view text, Error* error)
|
||||
{
|
||||
const std::string_view format_view(format);
|
||||
const size_t format_space = format_view.find_first_of(" \t\r\n");
|
||||
const std::string_view format_operands = (format_space == std::string_view::npos) ?
|
||||
std::string_view() :
|
||||
StringUtil::StripWhitespace(format_view.substr(format_space));
|
||||
|
||||
std::array<std::string_view, 4> expected;
|
||||
std::array<std::string_view, 4> actual;
|
||||
size_t expected_count;
|
||||
size_t actual_count;
|
||||
if (!SplitOperands(format_operands, &expected, &expected_count) ||
|
||||
!SplitOperands(GetOperands(text), &actual, &actual_count) || expected_count != actual_count)
|
||||
{
|
||||
Error::SetStringFmt(error, "Expected operands matching '{}'.", format);
|
||||
return false;
|
||||
}
|
||||
|
||||
const u32 cop_n = (*bits >> 26) & 3;
|
||||
for (size_t i = 0; i < expected_count; i++)
|
||||
{
|
||||
const std::string_view type = expected[i];
|
||||
const std::string_view operand = actual[i];
|
||||
u32 reg;
|
||||
|
||||
if (type == "$rs" || type == "$rt" || type == "$rt_" || type == "$rd")
|
||||
{
|
||||
if (!ParseGPR(operand, ®))
|
||||
{
|
||||
Error::SetStringFmt(error, "Invalid general-purpose register '{}'.", operand);
|
||||
return false;
|
||||
}
|
||||
|
||||
const u32 shift = (type == "$rs") ? 21 : ((type == "$rd") ? 11 : 16);
|
||||
SetField(bits, shift, 5, reg);
|
||||
}
|
||||
else if (type == "$coprd" || type == "$coprdc" || type == "$coprt")
|
||||
{
|
||||
const bool control = (type == "$coprdc");
|
||||
if (!ParseCopRegister(operand, cop_n, control, ®))
|
||||
{
|
||||
Error::SetStringFmt(error, "Invalid coprocessor register '{}'.", operand);
|
||||
return false;
|
||||
}
|
||||
SetField(bits, (type == "$coprt") ? 16 : 11, 5, reg);
|
||||
}
|
||||
else if (type == "$shamt")
|
||||
{
|
||||
u64 value;
|
||||
if (!ParseUnsignedValue(operand, &value) || value >= 32)
|
||||
{
|
||||
Error::SetStringFmt(error, "Shift amount '{}' is outside the range 0..31.", operand);
|
||||
return false;
|
||||
}
|
||||
SetField(bits, 6, 5, static_cast<u32>(value));
|
||||
}
|
||||
else if (type == "$imm")
|
||||
{
|
||||
s64 value;
|
||||
if (!ParseSignedValue(operand, &value) || value < std::numeric_limits<s16>::min() ||
|
||||
value > std::numeric_limits<s16>::max())
|
||||
{
|
||||
Error::SetStringFmt(error, "Immediate '{}' does not fit in a signed 16-bit field.", operand);
|
||||
return false;
|
||||
}
|
||||
SetField(bits, 0, 16, static_cast<u16>(value));
|
||||
}
|
||||
else if (type == "$immu" || type == "$immx")
|
||||
{
|
||||
u64 value;
|
||||
if (!ParseUnsignedValue(operand, &value) || value > std::numeric_limits<u16>::max())
|
||||
{
|
||||
Error::SetStringFmt(error, "Immediate '{}' does not fit in an unsigned 16-bit field.", operand);
|
||||
return false;
|
||||
}
|
||||
SetField(bits, 0, 16, static_cast<u32>(value));
|
||||
}
|
||||
else if (type == "$rel")
|
||||
{
|
||||
u64 target_value;
|
||||
if (!ParseUnsignedValue(operand, &target_value) || target_value > std::numeric_limits<u32>::max() ||
|
||||
(target_value & 3) != 0)
|
||||
{
|
||||
Error::SetStringFmt(error, "Branch target '{}' is not an aligned 32-bit address.", operand);
|
||||
return false;
|
||||
}
|
||||
|
||||
const u32 delta = static_cast<u32>(target_value) - (pc + 4);
|
||||
const s64 word_delta = static_cast<s64>(static_cast<s32>(delta)) / 4;
|
||||
if ((delta & 3) != 0 || word_delta < std::numeric_limits<s16>::min() ||
|
||||
word_delta > std::numeric_limits<s16>::max())
|
||||
{
|
||||
Error::SetStringFmt(error, "Branch target '{}' is out of range.", operand);
|
||||
return false;
|
||||
}
|
||||
SetField(bits, 0, 16, static_cast<u16>(word_delta));
|
||||
}
|
||||
else if (type == "$jt")
|
||||
{
|
||||
u64 target_value;
|
||||
if (!ParseUnsignedValue(operand, &target_value) || target_value > std::numeric_limits<u32>::max() ||
|
||||
(target_value & 3) != 0)
|
||||
{
|
||||
Error::SetStringFmt(error, "Jump target '{}' is not an aligned 32-bit address.", operand);
|
||||
return false;
|
||||
}
|
||||
|
||||
const u32 target = static_cast<u32>(target_value);
|
||||
if ((target & 0xF0000000u) != ((pc + 4) & 0xF0000000u))
|
||||
{
|
||||
Error::SetStringFmt(error, "Jump target '{}' is outside the current 256 MB region.", operand);
|
||||
return false;
|
||||
}
|
||||
SetField(bits, 0, 26, target >> 2);
|
||||
}
|
||||
else if (type == "$offsetrs")
|
||||
{
|
||||
const size_t left_paren = operand.find('(');
|
||||
const size_t right_paren = operand.rfind(')');
|
||||
if (left_paren == std::string_view::npos || right_paren != operand.size() - 1 || left_paren >= right_paren)
|
||||
{
|
||||
Error::SetStringFmt(error, "Invalid memory operand '{}'.", operand);
|
||||
return false;
|
||||
}
|
||||
|
||||
s64 offset;
|
||||
if (!ParseSignedValue(operand.substr(0, left_paren), &offset) || offset < std::numeric_limits<s16>::min() ||
|
||||
offset > std::numeric_limits<s16>::max())
|
||||
{
|
||||
Error::SetStringFmt(error, "Memory offset in '{}' does not fit in a signed 16-bit field.", operand);
|
||||
return false;
|
||||
}
|
||||
if (!ParseGPR(operand.substr(left_paren + 1, right_paren - left_paren - 1), ®))
|
||||
{
|
||||
Error::SetStringFmt(error, "Invalid base register in '{}'.", operand);
|
||||
return false;
|
||||
}
|
||||
SetField(bits, 21, 5, reg);
|
||||
SetField(bits, 0, 16, static_cast<u16>(offset));
|
||||
}
|
||||
else
|
||||
{
|
||||
Error::SetStringFmt(error, "Unsupported assembler operand type '{}'.", type);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
CPU::AssembleResult CPU::TryTableEntry(u32* dest, u32 pc, std::string_view text, const char* format, u32 base_bits,
|
||||
Error* error)
|
||||
{
|
||||
if (!format) // Unknown
|
||||
return AssembleResult::NoMatch;
|
||||
|
||||
const u32 cop_n = (base_bits >> 26) & 3;
|
||||
if (!StringUtil::EqualNoCase(GetMnemonic(text), GetFormatMnemonic(format, cop_n)))
|
||||
return AssembleResult::NoMatch;
|
||||
|
||||
u32 bits = base_bits;
|
||||
if (!AssembleFormat(&bits, pc, format, text, error))
|
||||
return AssembleResult::Error;
|
||||
|
||||
*dest = bits;
|
||||
return AssembleResult::Success;
|
||||
}
|
||||
|
||||
CPU::AssembleResult CPU::AssembleGTEInstruction(u32* dest, std::string_view text, Error* error)
|
||||
{
|
||||
const std::string_view mnemonic = GetMnemonic(text);
|
||||
const GTEInstructionTable* entry = nullptr;
|
||||
u32 command = 0;
|
||||
for (u32 i = 0; i < s_gte_instructions.size(); i++)
|
||||
{
|
||||
if (s_gte_instructions[i].name && StringUtil::EqualNoCase(mnemonic, s_gte_instructions[i].name))
|
||||
{
|
||||
entry = &s_gte_instructions[i];
|
||||
command = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!entry)
|
||||
return AssembleResult::NoMatch;
|
||||
|
||||
u32 bits = (static_cast<u32>(InstructionOp::cop2) << 26) | (UINT32_C(1) << 25) | command;
|
||||
std::string_view options = GetOperands(text);
|
||||
bool seen_sf = false;
|
||||
bool seen_lm = false;
|
||||
bool seen_m = false;
|
||||
bool seen_v = false;
|
||||
bool seen_t = false;
|
||||
while (!options.empty())
|
||||
{
|
||||
const size_t end = options.find_first_of(" \t\r\n");
|
||||
const std::string_view option = options.substr(0, end);
|
||||
options = (end == std::string_view::npos) ? std::string_view() : StringUtil::StripWhitespace(options.substr(end));
|
||||
|
||||
if (StringUtil::EqualNoCase(option, "sf"))
|
||||
{
|
||||
if (!entry->sf || seen_sf)
|
||||
{
|
||||
Error::SetStringFmt(error, "Invalid or duplicate GTE option '{}'.", option);
|
||||
return AssembleResult::Error;
|
||||
}
|
||||
seen_sf = true;
|
||||
bits |= UINT32_C(1) << 19;
|
||||
}
|
||||
else if (StringUtil::EqualNoCase(option, "lm"))
|
||||
{
|
||||
if (!entry->lm || seen_lm)
|
||||
{
|
||||
Error::SetStringFmt(error, "Invalid or duplicate GTE option '{}'.", option);
|
||||
return AssembleResult::Error;
|
||||
}
|
||||
seen_lm = true;
|
||||
bits |= UINT32_C(1) << 10;
|
||||
}
|
||||
else
|
||||
{
|
||||
const size_t equals = option.find('=');
|
||||
if (!entry->mvmva || equals == std::string_view::npos || equals != 1)
|
||||
{
|
||||
Error::SetStringFmt(error, "Invalid GTE option '{}'.", option);
|
||||
return AssembleResult::Error;
|
||||
}
|
||||
|
||||
u64 value;
|
||||
if (!ParseUnsignedValue(option.substr(2), &value) || value >= 4)
|
||||
{
|
||||
Error::SetStringFmt(error, "GTE option '{}' must have a value from 0 to 3.", option);
|
||||
return AssembleResult::Error;
|
||||
}
|
||||
|
||||
const char name = StringUtil::ToLower(option[0]);
|
||||
bool* seen = (name == 'm') ? &seen_m : ((name == 'v') ? &seen_v : ((name == 't') ? &seen_t : nullptr));
|
||||
const u32 shift = (name == 'm') ? 17 : ((name == 'v') ? 15 : 13);
|
||||
if (!seen || *seen)
|
||||
{
|
||||
Error::SetStringFmt(error, "Invalid or duplicate GTE option '{}'.", option);
|
||||
return AssembleResult::Error;
|
||||
}
|
||||
*seen = true;
|
||||
bits |= static_cast<u32>(value) << shift;
|
||||
}
|
||||
}
|
||||
|
||||
if (entry->mvmva && (!seen_m || !seen_v || !seen_t))
|
||||
{
|
||||
Error::SetStringView(error, "mvmva requires m=, v=, and t= options.");
|
||||
return AssembleResult::Error;
|
||||
}
|
||||
|
||||
*dest = bits;
|
||||
return AssembleResult::Success;
|
||||
}
|
||||
|
||||
bool CPU::AssembleInstruction(u32* dest, u32 pc, std::string_view text, Error* error)
|
||||
{
|
||||
if (!dest)
|
||||
{
|
||||
Error::SetStringView(error, "Destination pointer is null.");
|
||||
return false;
|
||||
}
|
||||
|
||||
text = StringUtil::StripWhitespace(text);
|
||||
if (text.empty())
|
||||
{
|
||||
Error::SetStringView(error, "Instruction is empty.");
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::string_view mnemonic = GetMnemonic(text);
|
||||
if (StringUtil::EqualNoCase(mnemonic, "nop"))
|
||||
{
|
||||
const AssembleResult result = TryTableEntry(dest, pc, text, "nop", 0, error);
|
||||
return (result == AssembleResult::Success);
|
||||
}
|
||||
|
||||
for (u32 op = 0; op < s_base_table.size(); op++)
|
||||
{
|
||||
if (op == static_cast<u32>(InstructionOp::funct) || op == static_cast<u32>(InstructionOp::b) ||
|
||||
(op >= static_cast<u32>(InstructionOp::cop0) && op <= static_cast<u32>(InstructionOp::cop3)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const AssembleResult result = TryTableEntry(dest, pc, text, s_base_table[op], op << 26, error);
|
||||
if (result != AssembleResult::NoMatch)
|
||||
return (result == AssembleResult::Success);
|
||||
}
|
||||
|
||||
for (u32 funct = 0; funct < s_special_table.size(); funct++)
|
||||
{
|
||||
const AssembleResult result = TryTableEntry(dest, pc, text, s_special_table[funct], funct, error);
|
||||
if (result != AssembleResult::NoMatch)
|
||||
return (result == AssembleResult::Success);
|
||||
}
|
||||
|
||||
static constexpr std::array<std::pair<u32, const char*>, 4> branch_table = {{
|
||||
{0, "bltz $rs, $rel"},
|
||||
{1, "bgez $rs, $rel"},
|
||||
{16, "bltzal $rs, $rel"},
|
||||
{17, "bgezal $rs, $rel"},
|
||||
}};
|
||||
for (const auto& [rt, format] : branch_table)
|
||||
{
|
||||
const u32 base_bits = (static_cast<u32>(InstructionOp::b) << 26) | (rt << 16);
|
||||
const AssembleResult result = TryTableEntry(dest, pc, text, format, base_bits, error);
|
||||
if (result != AssembleResult::NoMatch)
|
||||
return (result == AssembleResult::Success);
|
||||
}
|
||||
|
||||
for (u32 cop_n = 0; cop_n < 4; cop_n++)
|
||||
{
|
||||
for (const auto& [common_op, format] : s_cop_common_table)
|
||||
{
|
||||
const u32 base_bits =
|
||||
((static_cast<u32>(InstructionOp::cop0) + cop_n) << 26) | (static_cast<u32>(common_op) << 21);
|
||||
const AssembleResult result = TryTableEntry(dest, pc, text, format, base_bits, error);
|
||||
if (result != AssembleResult::NoMatch)
|
||||
return (result == AssembleResult::Success);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& [cop0_op, format] : s_cop0_table)
|
||||
{
|
||||
const u32 base_bits =
|
||||
(static_cast<u32>(InstructionOp::cop0) << 26) | (UINT32_C(1) << 25) | static_cast<u32>(cop0_op);
|
||||
const AssembleResult result = TryTableEntry(dest, pc, text, format, base_bits, error);
|
||||
if (result != AssembleResult::NoMatch)
|
||||
return (result == AssembleResult::Success);
|
||||
}
|
||||
|
||||
const AssembleResult gte_result = AssembleGTEInstruction(dest, text, error);
|
||||
if (gte_result != AssembleResult::NoMatch)
|
||||
return (gte_result == AssembleResult::Success);
|
||||
|
||||
if (mnemonic.size() == 4 && StringUtil::StartsWithNoCase(mnemonic, "cop") && mnemonic[3] >= '0' && mnemonic[3] <= '3')
|
||||
{
|
||||
u64 immediate;
|
||||
if (!ParseUnsignedValue(GetOperands(text), &immediate) || immediate > 0x1FFFFFF)
|
||||
{
|
||||
Error::SetStringView(error, "Raw coprocessor instruction requires a 25-bit immediate.");
|
||||
return false;
|
||||
}
|
||||
|
||||
const u32 cop_n = static_cast<u32>(mnemonic[3] - '0');
|
||||
*dest = ((static_cast<u32>(InstructionOp::cop0) + cop_n) << 26) | (UINT32_C(1) << 25) | static_cast<u32>(immediate);
|
||||
return true;
|
||||
}
|
||||
|
||||
Error::SetStringFmt(error, "Unknown instruction mnemonic '{}'.", mnemonic);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -4,10 +4,14 @@
|
||||
#pragma once
|
||||
#include "cpu_types.h"
|
||||
|
||||
#include <string_view>
|
||||
|
||||
class Error;
|
||||
class SmallStringBase;
|
||||
|
||||
namespace CPU {
|
||||
|
||||
bool AssembleInstruction(u32* dest, u32 pc, std::string_view text, Error* error = nullptr);
|
||||
void DisassembleInstruction(SmallStringBase* dest, u32 pc, u32 bits);
|
||||
void DisassembleInstructionComment(SmallStringBase* dest, u32 pc, u32 bits);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user