Cheats: Support branch labels in assembly codes

This commit is contained in:
Stenzek
2026-07-03 17:02:46 +10:00
parent fce1b1e30f
commit 7bed00d02d
4 changed files with 386 additions and 58 deletions
+162
View File
@@ -10,6 +10,7 @@
#include <gtest/gtest.h>
#include <array>
#include <limits>
namespace {
@@ -34,6 +35,30 @@ struct AssemblyCase
u32 bits;
};
struct LabelAssemblyCase
{
std::string_view label_text;
std::string_view forward_text;
std::string_view backward_text;
};
struct LabelAssemblyInstructions
{
u32* instructions;
size_t count;
u32 base_pc;
};
static u32* GetLabelAssemblyInstruction(const u32 pc, void* userdata)
{
const LabelAssemblyInstructions* const code = static_cast<LabelAssemblyInstructions*>(userdata);
if (pc < code->base_pc || ((pc - code->base_pc) % CPU::INSTRUCTION_SIZE) != 0)
return nullptr;
const size_t index = (pc - code->base_pc) / CPU::INSTRUCTION_SIZE;
return (index < code->count) ? &code->instructions[index] : nullptr;
}
TEST(CPUDisasm, Disassemble)
{
EXPECT_EQ(Disassemble(0x1000, 0x00000000), "sll zero, zero, 0");
@@ -140,6 +165,95 @@ TEST(CPUDisasm, Assemble)
EXPECT_EQ(Assemble(0x1000, test.text), test.bits) << test.text;
}
TEST(CPUDisasm, JumpTargets)
{
struct JumpType
{
std::string_view mnemonic;
u32 opcode;
};
static constexpr std::array jump_types = {
JumpType{"j", 0x08000000},
JumpType{"jal", 0x0C000000},
};
static constexpr std::array<u32, 5> regions = {0x00000000, 0x10000000, 0x80000000, 0xA0000000, 0xF0000000};
static constexpr std::array<u32, 4> target_fields = {0x0000000, 0x0000001, 0x0123456, 0x3FFFFFF};
for (const JumpType& jump : jump_types)
{
for (const u32 region : regions)
{
const u32 pc = region | 0x1000;
for (const u32 target_field : target_fields)
{
const u32 target = region | (target_field << 2);
const u32 bits = jump.opcode | target_field;
const std::string text = fmt::format("{} 0x{:08x}", jump.mnemonic, target);
SCOPED_TRACE(fmt::format("pc=0x{:08x}, instruction={}", pc, text));
EXPECT_EQ(Assemble(pc, text), bits);
EXPECT_EQ(Disassemble(pc, bits), text);
}
}
}
static constexpr std::array boundary_cases = {
AssemblyCase{"j 0x10000000", 0x08000000},
AssemblyCase{"jal 0x10000000", 0x0C000000},
};
for (const AssemblyCase& test : boundary_cases)
{
EXPECT_EQ(Assemble(0x0FFFFFFC, test.text), test.bits);
EXPECT_EQ(Disassemble(0x0FFFFFFC, test.bits), test.text);
}
EXPECT_EQ(Assemble(0xFFFFFFFC, "j 0x00000000"), 0x08000000u);
EXPECT_EQ(Disassemble(0xFFFFFFFC, 0x08000000), "j 0x00000000");
}
TEST(CPUDisasm, BranchTargetBoundaries)
{
static constexpr std::array<s32, 5> word_offsets = {
std::numeric_limits<s16>::min(), -1, 0, 1, std::numeric_limits<s16>::max(),
};
static constexpr u32 pc = 0x80010000;
for (const s32 word_offset : word_offsets)
{
const u32 target = (pc + 4) + static_cast<u32>(word_offset * 4);
const u32 bits = 0x11090000 | static_cast<u16>(word_offset);
const std::string text = fmt::format("beq t0, t1, 0x{:08x}", target);
SCOPED_TRACE(text);
EXPECT_EQ(Assemble(pc, text), bits);
EXPECT_EQ(Disassemble(pc, bits), text);
}
EXPECT_EQ(Assemble(0xFFFFFFFC, "beq t0, t1, 0x00000000"), 0x11090000u);
EXPECT_EQ(Disassemble(0xFFFFFFFC, 0x11090000), "beq t0, t1, 0x00000000");
}
TEST(CPUDisasm, ControlFlowTargetErrorsDoNotModifyDestination)
{
struct ErrorCase
{
u32 pc;
std::string_view text;
};
static constexpr std::array cases = {
ErrorCase{0x80001000, "j 0x90001000"}, ErrorCase{0x80001000, "jal 0x80001002"},
ErrorCase{0x0FFFFFF8, "j 0x10000000"}, ErrorCase{0x80010000, "beq t0, t1, 0x80030004"},
ErrorCase{0x80010000, "beq t0, t1, 0x7fff0000"},
};
for (const ErrorCase& test : cases)
{
u32 result = 0xDEADBEEF;
Error error;
EXPECT_FALSE(CPU::AssembleInstruction(&result, test.pc, test.text, &error)) << test.text;
EXPECT_EQ(result, 0xDEADBEEFu) << test.text;
EXPECT_TRUE(error.IsValid()) << test.text;
}
}
TEST(CPUDisasm, AssembleErrorsDoNotModifyDestination)
{
static constexpr std::array<std::string_view, 14> cases = {
@@ -160,6 +274,54 @@ TEST(CPUDisasm, AssembleErrorsDoNotModifyDestination)
}
}
TEST(CPUDisasm, AssembleBranchLabels)
{
static constexpr std::array cases = {
LabelAssemblyCase{"j @target", "j 0x1040", "j 0x1000"},
LabelAssemblyCase{"jal @target", "jal 0x1040", "jal 0x1000"},
LabelAssemblyCase{"beq t1, t2, @target", "beq t1, t2, 0x1040", "beq t1, t2, 0x1000"},
LabelAssemblyCase{"bne t1, t2, @target", "bne t1, t2, 0x1040", "bne t1, t2, 0x1000"},
LabelAssemblyCase{"blez t1, @target", "blez t1, 0x1040", "blez t1, 0x1000"},
LabelAssemblyCase{"bgtz t1, @target", "bgtz t1, 0x1040", "bgtz t1, 0x1000"},
LabelAssemblyCase{"bltz t0, @target", "bltz t0, 0x1040", "bltz t0, 0x1000"},
LabelAssemblyCase{"bgez t0, @target", "bgez t0, 0x1040", "bgez t0, 0x1000"},
LabelAssemblyCase{"bltzal t0, @target", "bltzal t0, 0x1040", "bltzal t0, 0x1000"},
LabelAssemblyCase{"bgezal t0, @target", "bgezal t0, 0x1040", "bgezal t0, 0x1000"},
};
CPU::AssemblyLabelList forward_labels;
std::array<u32, cases.size()> forward_results = {};
for (size_t i = 0; i < cases.size(); i++)
{
Error error;
ASSERT_TRUE(CPU::AssembleInstruction(&forward_results[i], 0x1000 + static_cast<u32>(i * 4), cases[i].label_text,
&forward_labels, &error))
<< error.GetDescription();
}
EXPECT_TRUE(CPU::ContainsUnresolvedLabels(forward_labels));
Error error;
CPU::AssemblyLabel* label;
ASSERT_TRUE((label = CPU::DefineAssemblyLabel(&forward_labels, "@target", 0x1040, &error))) << error.GetDescription();
EXPECT_FALSE(CPU::ContainsUnresolvedLabels(forward_labels));
LabelAssemblyInstructions forward_code = {forward_results.data(), forward_results.size(), 0x1000};
ASSERT_TRUE(CPU::FixupAssemblyLabelBackreferences(label, &forward_code, &error, GetLabelAssemblyInstruction))
<< error.GetDescription();
for (size_t i = 0; i < cases.size(); i++)
EXPECT_EQ(forward_results[i], Assemble(0x1000 + static_cast<u32>(i * 4), cases[i].forward_text));
CPU::AssemblyLabelList backward_labels;
ASSERT_TRUE(CPU::DefineAssemblyLabel(&backward_labels, "@target", 0x1000, &error)) << error.GetDescription();
for (size_t i = 0; i < cases.size(); i++)
{
const u32 pc = 0x1040 + static_cast<u32>(i * 4);
u32 result;
ASSERT_TRUE(CPU::AssembleInstruction(&result, pc, cases[i].label_text, &backward_labels, &error))
<< error.GetDescription();
EXPECT_EQ(result, Assemble(pc, cases[i].backward_text));
}
}
TEST(CPUDisasm, RoundTrip)
{
static constexpr std::array<u32, 54> instructions = {
+61 -29
View File
@@ -4617,10 +4617,10 @@ std::unique_ptr<Cheats::AssemblyCheatCode> Cheats::AssemblyCheatCode::Parse(Meta
}
std::unique_ptr<AssemblyCheatCode> code = std::make_unique<AssemblyCheatCode>(std::move(metadata));
code->instructions.reserve(static_cast<size_t>(std::ranges::count(data, '\n')) + 1);
CPU::AssemblyLabelList labels;
CheatFileReader reader(data);
std::optional<u32> pc;
u32 section_line = 0;
bool section_has_instruction = false;
std::string_view line;
while (reader.GetLine(&line))
{
@@ -4631,14 +4631,47 @@ std::unique_ptr<Cheats::AssemblyCheatCode> Cheats::AssemblyCheatCode::Parse(Meta
if (linev.empty())
continue;
if (linev.starts_with(".org "))
const size_t colon_pos = linev.find(':');
if (colon_pos != std::string_view::npos)
{
if (pc.has_value() && !section_has_instruction)
const std::string_view label_name = StringUtil::StripWhitespace(linev.substr(0, colon_pos));
if (label_name.empty() || label_name.front() == '+' || label_name.front() == '-' ||
(label_name.front() >= '0' && label_name.front() <= '9') || label_name.find('?') != std::string_view::npos ||
label_name.find_first_of(" \t\r\n") != std::string_view::npos)
{
Error::SetStringFmt(error, "No instructions after address at line {}.", section_line);
Error::SetStringFmt(error, "Malformed label at line {}: {}", reader.GetCurrentLineNumber(), linev);
return {};
}
if (!pc.has_value())
{
Error::SetStringFmt(error, "Label before starting address at line {}: {}", reader.GetCurrentLineNumber(),
linev);
return {};
}
CPU::AssemblyLabel* label = CPU::DefineAssemblyLabel(&labels, label_name, pc.value(), error);
if (!label)
return {};
if (!CPU::FixupAssemblyLabelBackreferences(label, code.get(), error, [](u32 pc, void* userdata) {
for (Instruction& inst : static_cast<AssemblyCheatCode*>(userdata)->instructions)
{
if (inst.pc == pc)
return &inst.new_value;
}
return static_cast<u32*>(nullptr);
}))
{
return {};
}
linev = StringUtil::StripWhitespace(linev.substr(colon_pos + 1));
if (linev.empty())
continue;
}
if (linev.starts_with(".org "))
{
std::string_view address_text = StringUtil::StripWhitespace(linev.substr(4));
if (address_text.starts_with("0x") || address_text.starts_with("0X"))
address_text.remove_prefix(2);
@@ -4657,8 +4690,6 @@ std::unique_ptr<Cheats::AssemblyCheatCode> Cheats::AssemblyCheatCode::Parse(Meta
}
pc = address.value();
section_line = reader.GetCurrentLineNumber();
section_has_instruction = false;
continue;
}
@@ -4669,12 +4700,22 @@ std::unique_ptr<Cheats::AssemblyCheatCode> Cheats::AssemblyCheatCode::Parse(Meta
return {};
}
u32 bits;
const u32 instruction_pc = pc.value();
if (std::ranges::any_of(code->instructions,
[instruction_pc](const Instruction& inst) { return inst.pc == instruction_pc; }))
{
Error::SetStringFmt(error, "Duplicate instruction address 0x{:08X} at line {}.", instruction_pc,
reader.GetCurrentLineNumber());
return {};
}
code->instructions.emplace_back(instruction_pc, 0, UNINITIALIZED_OLD_VALUE);
u32& bits = code->instructions.back().new_value;
Error assemble_error;
const size_t wildcard_pos = linev.find('?');
if (wildcard_pos == std::string_view::npos)
{
if (!CPU::AssembleInstruction(&bits, static_cast<u32>(pc.value()), linev, &assemble_error))
if (!CPU::AssembleInstruction(&bits, instruction_pc, linev, &labels, &assemble_error))
{
Error::SetStringFmt(error, "Failed to assemble instruction at line {}: {}", reader.GetCurrentLineNumber(),
assemble_error.GetDescription());
@@ -4709,8 +4750,8 @@ std::unique_ptr<Cheats::AssemblyCheatCode> Cheats::AssemblyCheatCode::Parse(Meta
}
u32 one_bits;
if (!CPU::AssembleInstruction(&bits, static_cast<u32>(pc.value()), zero_instruction, &assemble_error) ||
!CPU::AssembleInstruction(&one_bits, static_cast<u32>(pc.value()), one_instruction, &assemble_error))
if (!CPU::AssembleInstruction(&bits, instruction_pc, zero_instruction, &labels, &assemble_error) ||
!CPU::AssembleInstruction(&one_bits, instruction_pc, one_instruction, &labels, &assemble_error))
{
Error::SetStringFmt(error, "Failed to assemble wildcard instruction at line {}: {}",
reader.GetCurrentLineNumber(), assemble_error.GetDescription());
@@ -4730,35 +4771,26 @@ std::unique_ptr<Cheats::AssemblyCheatCode> Cheats::AssemblyCheatCode::Parse(Meta
return {};
}
code->option_instruction_values.emplace_back(static_cast<u32>(code->instructions.size()),
code->option_instruction_values.emplace_back(static_cast<u32>(code->instructions.size() - 1),
static_cast<u8>(option_bit_position),
static_cast<u8>(option_bit_count));
}
const u32 instruction_pc = static_cast<u32>(pc.value());
if (std::ranges::any_of(code->instructions,
[instruction_pc](const Instruction& inst) { return inst.pc == instruction_pc; }))
{
Error::SetStringFmt(error, "Duplicate instruction address 0x{:08X} at line {}.", instruction_pc,
reader.GetCurrentLineNumber());
return {};
}
code->instructions.push_back({instruction_pc, bits, UNINITIALIZED_OLD_VALUE});
section_has_instruction = true;
pc = pc.value() + CPU::INSTRUCTION_SIZE;
pc = instruction_pc + CPU::INSTRUCTION_SIZE;
}
if (pc.has_value() && !section_has_instruction)
{
Error::SetStringFmt(error, "No instructions after address at line {}.", section_line);
return {};
}
if (code->instructions.empty())
{
Error::SetStringView(error, "No instructions in code.");
return {};
}
if (CPU::ContainsUnresolvedLabels(labels))
{
const auto iter =
std::ranges::find_if(labels, [](const CPU::AssemblyLabel& label) { return !label.address.has_value(); });
Error::SetStringFmt(error, "Undefined label '{}'.", iter->name);
return {};
}
return code;
}
+146 -29
View File
@@ -55,9 +55,13 @@ 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 bool EncodeAssemblyTarget(u32* bits, u32 pc, u32 target, bool jump, Error* error);
static std::optional<u32> ResolveAssemblyTarget(AssemblyLabelList* labels, std::string_view operand, u32 placeholder,
u32* instruction, u32 pc);
static bool AssembleFormat(u32* bits, u32* instruction, u32 pc, const char* format, std::string_view text,
AssemblyLabelList* labels, Error* error);
static AssembleResult TryTableEntry(u32* dest, u32 pc, std::string_view text, const char* format, u32 base_bits,
Error* error);
AssemblyLabelList* labels, Error* error);
static AssembleResult AssembleGTEInstruction(u32* dest, std::string_view text, Error* error);
static const std::array<const char*, 64> s_base_table = {{
@@ -966,7 +970,60 @@ bool CPU::SetField(u32* bits, u32 shift, u32 width, u32 value)
return true;
}
bool CPU::AssembleFormat(u32* bits, u32 pc, const char* format, std::string_view text, Error* error)
bool CPU::EncodeAssemblyTarget(u32* bits, const u32 pc, const u32 target, const bool jump, Error* error)
{
if (jump)
{
if ((target & 3) != 0 || (target & 0xF0000000u) != ((pc + 4) & 0xF0000000u))
{
Error::SetStringFmt(error, "Jump target 0x{:08X} is outside the current 256 MB region.", target);
return false;
}
SetField(bits, 0, 26, (target & ~0xF0000000u) >> 2);
return true;
}
const u32 delta = target - (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 0x{:08X} is out of range.", target);
return false;
}
SetField(bits, 0, 16, static_cast<u16>(word_delta));
return true;
}
std::optional<u32> CPU::ResolveAssemblyTarget(AssemblyLabelList* labels, const std::string_view operand,
const u32 placeholder, u32* instruction, const u32 pc)
{
if (!labels || operand.empty() || operand.front() == '+' || operand.front() == '-' ||
(operand.front() >= '0' && operand.front() <= '9'))
{
return std::nullopt;
}
const auto iter = std::ranges::find(*labels, operand, &AssemblyLabel::name);
if (iter == labels->end())
{
labels->emplace_back(std::string(operand), std::nullopt, std::vector<u32>());
labels->back().backreferences.push_back(pc);
return placeholder;
}
if (!iter->address.has_value())
{
if (std::ranges::find(iter->backreferences, pc) == iter->backreferences.end())
iter->backreferences.push_back(pc);
}
return iter->address.value_or(placeholder);
}
bool CPU::AssembleFormat(u32* bits, u32* instruction, u32 pc, const char* format, std::string_view text,
AssemblyLabelList* labels, Error* error)
{
const std::string_view format_view(format);
const size_t format_space = format_view.find_first_of(" \t\r\n");
@@ -1047,40 +1104,46 @@ bool CPU::AssembleFormat(u32* bits, u32 pc, const char* format, std::string_view
else if (type == "$rel")
{
u64 target_value;
if (!ParseUnsignedValue(operand, &target_value) || target_value > std::numeric_limits<u32>::max() ||
(target_value & 3) != 0)
if (!ParseUnsignedValue(operand, &target_value))
{
const std::optional<u32> label_target = ResolveAssemblyTarget(labels, operand, pc + 4, instruction, pc);
if (!label_target.has_value())
{
Error::SetStringFmt(error, "Branch target '{}' is not an aligned 32-bit address.", operand);
return false;
}
target_value = label_target.value();
}
if (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);
if (!EncodeAssemblyTarget(bits, pc, static_cast<u32>(target_value), false, error))
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)
if (!ParseUnsignedValue(operand, &target_value))
{
const std::optional<u32> label_target = ResolveAssemblyTarget(labels, operand, pc + 4, instruction, pc);
if (!label_target.has_value())
{
Error::SetStringFmt(error, "Jump target '{}' is not an aligned 32-bit address.", operand);
return false;
}
target_value = label_target.value();
}
if (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);
if (!EncodeAssemblyTarget(bits, pc, static_cast<u32>(target_value), true, error))
return false;
}
SetField(bits, 0, 26, target >> 2);
}
else if (type == "$offsetrs")
{
@@ -1118,7 +1181,7 @@ bool CPU::AssembleFormat(u32* bits, u32 pc, const char* format, std::string_view
}
CPU::AssembleResult CPU::TryTableEntry(u32* dest, u32 pc, std::string_view text, const char* format, u32 base_bits,
Error* error)
AssemblyLabelList* labels, Error* error)
{
if (!format) // Unknown
return AssembleResult::NoMatch;
@@ -1128,7 +1191,7 @@ CPU::AssembleResult CPU::TryTableEntry(u32* dest, u32 pc, std::string_view text,
return AssembleResult::NoMatch;
u32 bits = base_bits;
if (!AssembleFormat(&bits, pc, format, text, error))
if (!AssembleFormat(&bits, dest, pc, format, text, labels, error))
return AssembleResult::Error;
*dest = bits;
@@ -1225,6 +1288,11 @@ CPU::AssembleResult CPU::AssembleGTEInstruction(u32* dest, std::string_view text
}
bool CPU::AssembleInstruction(u32* dest, u32 pc, std::string_view text, Error* error)
{
return AssembleInstruction(dest, pc, text, nullptr, error);
}
bool CPU::AssembleInstruction(u32* dest, u32 pc, std::string_view text, AssemblyLabelList* labels, Error* error)
{
if (!dest)
{
@@ -1242,7 +1310,7 @@ bool CPU::AssembleInstruction(u32* dest, u32 pc, std::string_view text, Error* e
const std::string_view mnemonic = GetMnemonic(text);
if (StringUtil::EqualNoCase(mnemonic, "nop"))
{
const AssembleResult result = TryTableEntry(dest, pc, text, "nop", 0, error);
const AssembleResult result = TryTableEntry(dest, pc, text, "nop", 0, labels, error);
return (result == AssembleResult::Success);
}
@@ -1254,14 +1322,14 @@ bool CPU::AssembleInstruction(u32* dest, u32 pc, std::string_view text, Error* e
continue;
}
const AssembleResult result = TryTableEntry(dest, pc, text, s_base_table[op], op << 26, error);
const AssembleResult result = TryTableEntry(dest, pc, text, s_base_table[op], op << 26, labels, 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);
const AssembleResult result = TryTableEntry(dest, pc, text, s_special_table[funct], funct, labels, error);
if (result != AssembleResult::NoMatch)
return (result == AssembleResult::Success);
}
@@ -1275,7 +1343,7 @@ bool CPU::AssembleInstruction(u32* dest, u32 pc, std::string_view text, Error* e
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);
const AssembleResult result = TryTableEntry(dest, pc, text, format, base_bits, labels, error);
if (result != AssembleResult::NoMatch)
return (result == AssembleResult::Success);
}
@@ -1286,7 +1354,7 @@ bool CPU::AssembleInstruction(u32* dest, u32 pc, std::string_view text, Error* e
{
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);
const AssembleResult result = TryTableEntry(dest, pc, text, format, base_bits, labels, error);
if (result != AssembleResult::NoMatch)
return (result == AssembleResult::Success);
}
@@ -1296,7 +1364,7 @@ bool CPU::AssembleInstruction(u32* dest, u32 pc, std::string_view text, Error* e
{
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);
const AssembleResult result = TryTableEntry(dest, pc, text, format, base_bits, labels, error);
if (result != AssembleResult::NoMatch)
return (result == AssembleResult::Success);
}
@@ -1322,3 +1390,52 @@ bool CPU::AssembleInstruction(u32* dest, u32 pc, std::string_view text, Error* e
Error::SetStringFmt(error, "Unknown instruction mnemonic '{}'.", mnemonic);
return false;
}
bool CPU::ContainsUnresolvedLabels(const AssemblyLabelList& labels)
{
return std::ranges::any_of(labels, [](const AssemblyLabel& label) { return !label.address.has_value(); });
}
CPU::AssemblyLabel* CPU::DefineAssemblyLabel(AssemblyLabelList* labels, const std::string_view name, const u32 address,
Error* error)
{
const auto iter = std::ranges::find(*labels, name, &AssemblyLabel::name);
if (iter == labels->end())
{
labels->emplace_back(std::string(name), std::make_optional(address), std::vector<u32>());
return &labels->back();
}
if (iter->address.has_value())
{
Error::SetStringFmt(error, "Label '{}' is already defined.", name);
return nullptr;
}
iter->address = address;
return &(*iter);
}
bool CPU::FixupAssemblyLabelBackreferences(AssemblyLabel* label, void* userdata, Error* error,
u32* (*instruction_reader)(u32 pc, void* userdata))
{
for (const u32 fixup_pc : label->backreferences)
{
u32* instruction = instruction_reader(fixup_pc, userdata);
if (!instruction)
{
Error::SetStringFmt(error, "Failed to read instruction at 0x{:08X} for label backreference.", fixup_pc);
return false;
}
const InstructionOp op = Instruction{*instruction}.op;
if (!EncodeAssemblyTarget(instruction, fixup_pc, label->address.value(),
(op == InstructionOp::j || op == InstructionOp::jal), error))
{
return false;
}
}
label->backreferences.clear();
return true;
}
+17
View File
@@ -4,14 +4,31 @@
#pragma once
#include "cpu_types.h"
#include <optional>
#include <string>
#include <string_view>
#include <vector>
class Error;
class SmallStringBase;
namespace CPU {
struct AssemblyLabel
{
std::string name;
std::optional<u32> address;
std::vector<u32> backreferences; // pc list
};
using AssemblyLabelList = std::vector<AssemblyLabel>;
bool AssembleInstruction(u32* dest, u32 pc, std::string_view text, Error* error = nullptr);
bool AssembleInstruction(u32* dest, u32 pc, std::string_view text, AssemblyLabelList* labels, Error* error = nullptr);
AssemblyLabel* DefineAssemblyLabel(AssemblyLabelList* labels, std::string_view name, u32 address,
Error* error = nullptr);
bool FixupAssemblyLabelBackreferences(AssemblyLabel* label, void* userdata, Error* error,
u32* (*instruction_reader)(u32 pc, void* userdata));
bool ContainsUnresolvedLabels(const AssemblyLabelList& labels);
void DisassembleInstruction(SmallStringBase* dest, u32 pc, u32 bits);
void DisassembleInstructionComment(SmallStringBase* dest, u32 pc, u32 bits);