CPU: Add MIPS assembler

This commit is contained in:
Stenzek
2026-06-24 19:55:04 +10:00
parent 1ffee71757
commit b43807a2bf
10 changed files with 998 additions and 148 deletions

View File

@@ -29,6 +29,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "googletest", "dep\googletes
EndProject EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "common-tests", "src\common-tests\common-tests.vcxproj", "{EA2B9C7A-B8CC-42F9-879B-191A98680C10}" Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "common-tests", "src\common-tests\common-tests.vcxproj", "{EA2B9C7A-B8CC-42F9-879B-191A98680C10}"
EndProject 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}" Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "scmversion", "src\scmversion\scmversion.vcxproj", "{075CED82-6A20-46DF-94C7-9624AC9DDBEB}"
EndProject EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "updater", "src\updater\updater.vcxproj", "{32EEAF44-57F8-4C6C-A6F0-DE5667123DD5}" 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|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|ARM64.ActiveCfg = ReleaseLTCG-Clang|ARM64
{EA2B9C7A-B8CC-42F9-879B-191A98680C10}.ReleaseLTCG-Clang-SSE2|x64.ActiveCfg = ReleaseLTCG-Clang-SSE2|x64 {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|ARM64.ActiveCfg = Debug-Clang|ARM64
{075CED82-6A20-46DF-94C7-9624AC9DDBEB}.Debug|x64.ActiveCfg = Debug|x64 {075CED82-6A20-46DF-94C7-9624AC9DDBEB}.Debug|x64.ActiveCfg = Debug|x64
{075CED82-6A20-46DF-94C7-9624AC9DDBEB}.Debug|x64.Build.0 = Debug|x64 {075CED82-6A20-46DF-94C7-9624AC9DDBEB}.Debug|x64.Build.0 = Debug|x64

View File

@@ -14,5 +14,6 @@ endif()
if(BUILD_TESTS) if(BUILD_TESTS)
add_subdirectory(common-tests EXCLUDE_FROM_ALL) add_subdirectory(common-tests EXCLUDE_FROM_ALL)
add_subdirectory(core-tests EXCLUDE_FROM_ALL)
add_subdirectory(util-tests EXCLUDE_FROM_ALL) add_subdirectory(util-tests EXCLUDE_FROM_ALL)
endif() endif()

View 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)

View 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>

View 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>

View 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

View 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;
}

View File

@@ -11,6 +11,7 @@
#include <array> #include <array>
#include <optional> #include <optional>
#include <span>
#include <string> #include <string>
#include <vector> #include <vector>

View File

@@ -6,30 +6,21 @@
#include "cpu_types.h" #include "cpu_types.h"
#include "common/assert.h" #include "common/assert.h"
#include "common/error.h"
#include "common/small_string.h" #include "common/small_string.h"
#include "common/string_util.h"
#include <array> #include <array>
#include <limits>
namespace CPU { namespace CPU {
namespace { namespace {
enum Operand : u8 enum class AssembleResult
{ {
Operand_None, NoMatch,
i_rs, Success,
i_rt, Error,
i_imm,
j_target,
r_rs,
r_rt,
r_rd,
r_shamt,
r_funct
};
struct TableEntry
{
const char* format;
}; };
struct GTEInstructionTable 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 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 = {{ static const std::array<const char*, 64> s_base_table = {{
"", // 0 "", // 0
"UNKNOWN", // 1 nullptr, // 1
"j $jt", // 2 "j $jt", // 2
"jal $jt", // 3 "jal $jt", // 3
"beq $rs, $rt, $rel", // 4 "beq $rs, $rt, $rel", // 4
@@ -71,22 +77,22 @@ static const std::array<const char*, 64> s_base_table = {{
"ori $rt, $rs, $immx", // 13 "ori $rt, $rs, $immx", // 13
"xori $rt, $rs, $immx", // 14 "xori $rt, $rs, $immx", // 14
"lui $rt, $immx", // 15 "lui $rt, $immx", // 15
"UNKNOWN", // 16 nullptr, // 16
"UNKNOWN", // 17 nullptr, // 17
"UNKNOWN", // 18 nullptr, // 18
"UNKNOWN", // 19 nullptr, // 19
"UNKNOWN", // 20 nullptr, // 20
"UNKNOWN", // 21 nullptr, // 21
"UNKNOWN", // 22 nullptr, // 22
"UNKNOWN", // 23 nullptr, // 23
"UNKNOWN", // 24 nullptr, // 24
"UNKNOWN", // 25 nullptr, // 25
"UNKNOWN", // 26 nullptr, // 26
"UNKNOWN", // 27 nullptr, // 27
"UNKNOWN", // 28 nullptr, // 28
"UNKNOWN", // 29 nullptr, // 29
"UNKNOWN", // 30 nullptr, // 30
"UNKNOWN", // 31 nullptr, // 31
"lb $rt, $offsetrs", // 32 "lb $rt, $offsetrs", // 32
"lh $rt, $offsetrs", // 33 "lh $rt, $offsetrs", // 33
"lwl $rt, $offsetrs", // 34 "lwl $rt, $offsetrs", // 34
@@ -94,66 +100,66 @@ static const std::array<const char*, 64> s_base_table = {{
"lbu $rt, $offsetrs", // 36 "lbu $rt, $offsetrs", // 36
"lhu $rt, $offsetrs", // 37 "lhu $rt, $offsetrs", // 37
"lwr $rt, $offsetrs", // 38 "lwr $rt, $offsetrs", // 38
"UNKNOWN", // 39 nullptr, // 39
"sb $rt, $offsetrs", // 40 "sb $rt, $offsetrs", // 40
"sh $rt, $offsetrs", // 41 "sh $rt, $offsetrs", // 41
"swl $rt, $offsetrs", // 42 "swl $rt, $offsetrs", // 42
"sw $rt, $offsetrs", // 43 "sw $rt, $offsetrs", // 43
"UNKNOWN", // 44 nullptr, // 44
"UNKNOWN", // 45 nullptr, // 45
"swr $rt, $offsetrs", // 46 "swr $rt, $offsetrs", // 46
"UNKNOWN", // 47 nullptr, // 47
"lwc0 $coprt, $offsetrs", // 48 "lwc0 $coprt, $offsetrs", // 48
"lwc1 $coprt, $offsetrs", // 49 "lwc1 $coprt, $offsetrs", // 49
"lwc2 $coprt, $offsetrs", // 50 "lwc2 $coprt, $offsetrs", // 50
"lwc3 $coprt, $offsetrs", // 51 "lwc3 $coprt, $offsetrs", // 51
"UNKNOWN", // 52 nullptr, // 52
"UNKNOWN", // 53 nullptr, // 53
"UNKNOWN", // 54 nullptr, // 54
"UNKNOWN", // 55 nullptr, // 55
"swc0 $coprt, $offsetrs", // 56 "swc0 $coprt, $offsetrs", // 56
"swc1 $coprt, $offsetrs", // 57 "swc1 $coprt, $offsetrs", // 57
"swc2 $coprt, $offsetrs", // 58 "swc2 $coprt, $offsetrs", // 58
"swc3 $coprt, $offsetrs", // 59 "swc3 $coprt, $offsetrs", // 59
"UNKNOWN", // 60 nullptr, // 60
"UNKNOWN", // 61 nullptr, // 61
"UNKNOWN", // 62 nullptr, // 62
"UNKNOWN" // 63 nullptr // 63
}}; }};
static const std::array<const char*, 64> s_special_table = {{ static const std::array<const char*, 64> s_special_table = {{
"sll $rd, $rt, $shamt", // 0 "sll $rd, $rt, $shamt", // 0
"UNKNOWN", // 1 nullptr, // 1
"srl $rd, $rt, $shamt", // 2 "srl $rd, $rt, $shamt", // 2
"sra $rd, $rt, $shamt", // 3 "sra $rd, $rt, $shamt", // 3
"sllv $rd, $rt, $rs", // 4 "sllv $rd, $rt, $rs", // 4
"UNKNOWN", // 5 nullptr, // 5
"srlv $rd, $rt, $rs", // 6 "srlv $rd, $rt, $rs", // 6
"srav $rd, $rt, $rs", // 7 "srav $rd, $rt, $rs", // 7
"jr $rs", // 8 "jr $rs", // 8
"jalr $rd, $rs", // 9 "jalr $rd, $rs", // 9
"UNKNOWN", // 10 nullptr, // 10
"UNKNOWN", // 11 nullptr, // 11
"syscall", // 12 "syscall", // 12
"break", // 13 "break", // 13
"UNKNOWN", // 14 nullptr, // 14
"UNKNOWN", // 15 nullptr, // 15
"mfhi $rd", // 16 "mfhi $rd", // 16
"mthi $rs", // 17 "mthi $rs", // 17
"mflo $rd", // 18 "mflo $rd", // 18
"mtlo $rs", // 19 "mtlo $rs", // 19
"UNKNOWN", // 20 nullptr, // 20
"UNKNOWN", // 21 nullptr, // 21
"UNKNOWN", // 22 nullptr, // 22
"UNKNOWN", // 23 nullptr, // 23
"mult $rs, $rt", // 24 "mult $rs, $rt", // 24
"multu $rs, $rt", // 25 "multu $rs, $rt", // 25
"div $rs, $rt", // 26 "div $rs, $rt", // 26
"divu $rs, $rt", // 27 "divu $rs, $rt", // 27
"UNKNOWN", // 28 nullptr, // 28
"UNKNOWN", // 29 nullptr, // 29
"UNKNOWN", // 30 nullptr, // 30
"UNKNOWN", // 31 nullptr, // 31
"add $rd, $rs, $rt", // 32 "add $rd, $rs, $rt", // 32
"addu $rd, $rs, $rt", // 33 "addu $rd, $rs, $rt", // 33
"sub $rd, $rs, $rt", // 34 "sub $rd, $rs, $rt", // 34
@@ -162,30 +168,30 @@ static const std::array<const char*, 64> s_special_table = {{
"or $rd, $rs, $rt", // 37 "or $rd, $rs, $rt", // 37
"xor $rd, $rs, $rt", // 38 "xor $rd, $rs, $rt", // 38
"nor $rd, $rs, $rt", // 39 "nor $rd, $rs, $rt", // 39
"UNKNOWN", // 40 nullptr, // 40
"UNKNOWN", // 41 nullptr, // 41
"slt $rd, $rs, $rt", // 42 "slt $rd, $rs, $rt", // 42
"sltu $rd, $rs, $rt", // 43 "sltu $rd, $rs, $rt", // 43
"UNKNOWN", // 44 nullptr, // 44
"UNKNOWN", // 45 nullptr, // 45
"UNKNOWN", // 46 nullptr, // 46
"UNKNOWN", // 47 nullptr, // 47
"UNKNOWN", // 48 nullptr, // 48
"UNKNOWN", // 49 nullptr, // 49
"UNKNOWN", // 50 nullptr, // 50
"UNKNOWN", // 51 nullptr, // 51
"UNKNOWN", // 52 nullptr, // 52
"UNKNOWN", // 53 nullptr, // 53
"UNKNOWN", // 54 nullptr, // 54
"UNKNOWN", // 55 nullptr, // 55
"UNKNOWN", // 56 nullptr, // 56
"UNKNOWN", // 57 nullptr, // 57
"UNKNOWN", // 58 nullptr, // 58
"UNKNOWN", // 59 nullptr, // 59
"UNKNOWN", // 60 nullptr, // 60
"UNKNOWN", // 61 nullptr, // 61
"UNKNOWN", // 62 nullptr, // 62
"UNKNOWN" // 63 nullptr // 63
}}; }};
static const std::array<std::pair<CopCommonInstruction, const char*>, 4> s_cop_common_table = { 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"}}; "$22", "$23", "$24", "$25", "$26", "$27", "$28", "$29", "$30", "$31"}};
static constexpr const std::array<GTEInstructionTable, 64> s_gte_instructions = {{ static constexpr const std::array<GTEInstructionTable, 64> s_gte_instructions = {{
{"UNKNOWN", false, false, false}, // 0x00 {nullptr, false, false, false}, // 0x00
{"rtps", true, true, false}, // 0x01 {"rtps", true, true, false}, // 0x01
{"UNKNOWN", false, false, false}, // 0x02 {nullptr, false, false, false}, // 0x02
{"UNKNOWN", false, false, false}, // 0x03 {nullptr, false, false, false}, // 0x03
{"UNKNOWN", false, false, false}, // 0x04 {nullptr, false, false, false}, // 0x04
{"UNKNOWN", false, false, false}, // 0x05 {nullptr, false, false, false}, // 0x05
{"nclip", false, false, false}, // 0x06 {"nclip", false, false, false}, // 0x06
{"UNKNOWN", false, false, false}, // 0x07 {nullptr, false, false, false}, // 0x07
{"UNKNOWN", false, false, false}, // 0x08 {nullptr, false, false, false}, // 0x08
{"UNKNOWN", false, false, false}, // 0x09 {nullptr, false, false, false}, // 0x09
{"UNKNOWN", false, false, false}, // 0x0A {nullptr, false, false, false}, // 0x0A
{"UNKNOWN", false, false, false}, // 0x0B {nullptr, false, false, false}, // 0x0B
{"op", true, true, false}, // 0x0C {"op", true, true, false}, // 0x0C
{"UNKNOWN", false, false, false}, // 0x0D {nullptr, false, false, false}, // 0x0D
{"UNKNOWN", false, false, false}, // 0x0E {nullptr, false, false, false}, // 0x0E
{"UNKNOWN", false, false, false}, // 0x0F {nullptr, false, false, false}, // 0x0F
{"dpcs", true, true, false}, // 0x10 {"dpcs", true, true, false}, // 0x10
{"intpl", true, true, false}, // 0x11 {"intpl", true, true, false}, // 0x11
{"mvmva", true, true, true}, // 0x12 {"mvmva", true, true, true}, // 0x12
{"ncds", true, true, false}, // 0x13 {"ncds", true, true, false}, // 0x13
{"cdp", true, true, false}, // 0x14 {"cdp", true, true, false}, // 0x14
{"UNKNOWN", false, false, false}, // 0x15 {nullptr, false, false, false}, // 0x15
{"ncdt", true, true, false}, // 0x16 {"ncdt", true, true, false}, // 0x16
{"UNKNOWN", false, false, false}, // 0x17 {nullptr, false, false, false}, // 0x17
{"UNKNOWN", false, false, false}, // 0x18 {nullptr, false, false, false}, // 0x18
{"UNKNOWN", false, false, false}, // 0x19 {nullptr, false, false, false}, // 0x19
{"UNKNOWN", false, false, false}, // 0x1A {nullptr, false, false, false}, // 0x1A
{"nccs", true, true, false}, // 0x1B {"nccs", true, true, false}, // 0x1B
{"cc", true, true, false}, // 0x1C {"cc", true, true, false}, // 0x1C
{"UNKNOWN", false, false, false}, // 0x1D {nullptr, false, false, false}, // 0x1D
{"ncs", true, true, false}, // 0x1E {"ncs", true, true, false}, // 0x1E
{"UNKNOWN", false, false, false}, // 0x1F {nullptr, false, false, false}, // 0x1F
{"nct", true, true, false}, // 0x20 {"nct", true, true, false}, // 0x20
{"UNKNOWN", false, false, false}, // 0x21 {nullptr, false, false, false}, // 0x21
{"UNKNOWN", false, false, false}, // 0x22 {nullptr, false, false, false}, // 0x22
{"UNKNOWN", false, false, false}, // 0x23 {nullptr, false, false, false}, // 0x23
{"UNKNOWN", false, false, false}, // 0x24 {nullptr, false, false, false}, // 0x24
{"UNKNOWN", false, false, false}, // 0x25 {nullptr, false, false, false}, // 0x25
{"UNKNOWN", false, false, false}, // 0x26 {nullptr, false, false, false}, // 0x26
{"UNKNOWN", false, false, false}, // 0x27 {nullptr, false, false, false}, // 0x27
{"sqr", true, true, false}, // 0x28 {"sqr", true, true, false}, // 0x28
{"dcpl", true, true, false}, // 0x29 {"dcpl", true, true, false}, // 0x29
{"dpct", true, true, false}, // 0x2A {"dpct", true, true, false}, // 0x2A
{"UNKNOWN", false, false, false}, // 0x2B {nullptr, false, false, false}, // 0x2B
{"UNKNOWN", false, false, false}, // 0x2C {nullptr, false, false, false}, // 0x2C
{"avsz3", false, false, false}, // 0x2D {"avsz3", false, false, false}, // 0x2D
{"avsz4", false, false, false}, // 0x2E {"avsz4", false, false, false}, // 0x2E
{"UNKNOWN", false, false, false}, // 0x2F {nullptr, false, false, false}, // 0x2F
{"rtpt", true, true, false}, // 0x30 {"rtpt", true, true, false}, // 0x30
{"UNKNOWN", false, false, false}, // 0x31 {nullptr, false, false, false}, // 0x31
{"UNKNOWN", false, false, false}, // 0x32 {nullptr, false, false, false}, // 0x32
{"UNKNOWN", false, false, false}, // 0x33 {nullptr, false, false, false}, // 0x33
{"UNKNOWN", false, false, false}, // 0x34 {nullptr, false, false, false}, // 0x34
{"UNKNOWN", false, false, false}, // 0x35 {nullptr, false, false, false}, // 0x35
{"UNKNOWN", false, false, false}, // 0x36 {nullptr, false, false, false}, // 0x36
{"UNKNOWN", false, false, false}, // 0x37 {nullptr, false, false, false}, // 0x37
{"UNKNOWN", false, false, false}, // 0x38 {nullptr, false, false, false}, // 0x38
{"UNKNOWN", false, false, false}, // 0x39 {nullptr, false, false, false}, // 0x39
{"UNKNOWN", false, false, false}, // 0x3A {nullptr, false, false, false}, // 0x3A
{"UNKNOWN", false, false, false}, // 0x3B {nullptr, false, false, false}, // 0x3B
{"UNKNOWN", false, false, false}, // 0x3C {nullptr, false, false, false}, // 0x3C
{"gpf", true, true, false}, // 0x3D {"gpf", true, true, false}, // 0x3D
{"gpl", true, true, false}, // 0x3E {"gpl", true, true, false}, // 0x3E
{"ncct", true, true, false}, // 0x3F {"ncct", true, true, false}, // 0x3F
}}; }};
} // namespace CPU } // namespace CPU
@@ -281,6 +287,12 @@ void CPU::FormatInstruction(SmallStringBase* dest, const Instruction inst, u32 p
{ {
dest->clear(); dest->clear();
if (!format)
{
dest->assign("UNKNOWN");
return;
}
const char* str = format; const char* str = format;
while (*str != '\0') 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) 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 CPU::Registers* regs = &CPU::g_state.regs;
const char* str = format; 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> template<typename T>
@@ -620,6 +635,12 @@ void CPU::FormatGTEInstruction(SmallStringBase* dest, u32 pc, const Instruction
{ {
const GTE::Instruction gi{inst.bits}; const GTE::Instruction gi{inst.bits};
const GTEInstructionTable& t = s_gte_instructions[gi.command]; const GTEInstructionTable& t = s_gte_instructions[gi.command];
if (!t.name)
{
dest->assign("UNKNOWN");
return;
}
dest->assign(t.name); dest->assign(t.name);
if (t.sf && gi.sf) if (t.sf && gi.sf)
@@ -673,7 +694,7 @@ void CPU::DisassembleInstruction(SmallStringBase* dest, u32 pc, u32 bits)
case InstructionOp::cop3: case InstructionOp::cop3:
default: 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; break;
} }
@@ -736,7 +757,7 @@ void CPU::DisassembleInstructionComment(SmallStringBase* dest, u32 pc, u32 bits)
case InstructionOp::cop3: case InstructionOp::cop3:
default: 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; break;
} }
@@ -772,3 +793,532 @@ const char* CPU::GetCop0RegisterName(u32 index)
{ {
return (index < s_cop0_register_names.size()) ? s_cop0_register_names[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, &reg))
{
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, &reg))
{
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), &reg))
{
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;
}

View File

@@ -4,10 +4,14 @@
#pragma once #pragma once
#include "cpu_types.h" #include "cpu_types.h"
#include <string_view>
class Error;
class SmallStringBase; class SmallStringBase;
namespace CPU { namespace CPU {
bool AssembleInstruction(u32* dest, u32 pc, std::string_view text, Error* error = nullptr);
void DisassembleInstruction(SmallStringBase* dest, u32 pc, u32 bits); void DisassembleInstruction(SmallStringBase* dest, u32 pc, u32 bits);
void DisassembleInstructionComment(SmallStringBase* dest, u32 pc, u32 bits); void DisassembleInstructionComment(SmallStringBase* dest, u32 pc, u32 bits);