mirror of
https://github.com/N64Recomp/N64Recomp.git
synced 2025-05-14 08:12:19 +00:00
Added store instruction operation table
This commit is contained in:
parent
dbd53ef9c4
commit
c4f85867f3
1 changed files with 130 additions and 74 deletions
|
@ -20,6 +20,19 @@ std::string_view ctx_gpr_prefix(int reg) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum class StoreOpType {
|
||||||
|
SD,
|
||||||
|
SDL,
|
||||||
|
SDR,
|
||||||
|
SW,
|
||||||
|
SWL,
|
||||||
|
SWR,
|
||||||
|
SH,
|
||||||
|
SB,
|
||||||
|
SDC1,
|
||||||
|
SWC1
|
||||||
|
};
|
||||||
|
|
||||||
enum class UnaryOpType {
|
enum class UnaryOpType {
|
||||||
None,
|
None,
|
||||||
ToS32,
|
ToS32,
|
||||||
|
@ -143,6 +156,11 @@ enum class Operand {
|
||||||
Base = Rs, // Alias for Rs for loads
|
Base = Rs, // Alias for Rs for loads
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct StoreOp {
|
||||||
|
StoreOpType type;
|
||||||
|
Operand value_input;
|
||||||
|
};
|
||||||
|
|
||||||
struct UnaryOp {
|
struct UnaryOp {
|
||||||
UnaryOpType operation;
|
UnaryOpType operation;
|
||||||
Operand output;
|
Operand output;
|
||||||
|
@ -348,6 +366,19 @@ const std::unordered_map<InstrId, ConditionalBranchOp> conditional_branch_ops {
|
||||||
{ InstrId::cpu_bc1tl, { BinaryOpType::Equal, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Cop1cs, Operand::Zero }}, false, true }},
|
{ InstrId::cpu_bc1tl, { BinaryOpType::Equal, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Cop1cs, Operand::Zero }}, false, true }},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const std::unordered_map<InstrId, StoreOp> store_ops {
|
||||||
|
{ InstrId::cpu_sd, { StoreOpType::SD, Operand::Rt }},
|
||||||
|
{ InstrId::cpu_sdl, { StoreOpType::SDL, Operand::Rt }},
|
||||||
|
{ InstrId::cpu_sdr, { StoreOpType::SDR, Operand::Rt }},
|
||||||
|
{ InstrId::cpu_sw, { StoreOpType::SW, Operand::Rt }},
|
||||||
|
{ InstrId::cpu_swl, { StoreOpType::SWL, Operand::Rt }},
|
||||||
|
{ InstrId::cpu_swr, { StoreOpType::SWR, Operand::Rt }},
|
||||||
|
{ InstrId::cpu_sh, { StoreOpType::SH, Operand::Rt }},
|
||||||
|
{ InstrId::cpu_sb, { StoreOpType::SB, Operand::Rt }},
|
||||||
|
{ InstrId::cpu_sdc1, { StoreOpType::SDC1, Operand::FtU64 }},
|
||||||
|
{ InstrId::cpu_swc1, { StoreOpType::SWC1, Operand::FtU32L }},
|
||||||
|
};
|
||||||
|
|
||||||
struct InstructionContext {
|
struct InstructionContext {
|
||||||
int rd;
|
int rd;
|
||||||
int rs;
|
int rs;
|
||||||
|
@ -372,6 +403,7 @@ public:
|
||||||
CGenerator() = default;
|
CGenerator() = default;
|
||||||
void process_binary_op(std::ostream& output_file, const BinaryOp& op, const InstructionContext& ctx) const;
|
void process_binary_op(std::ostream& output_file, const BinaryOp& op, const InstructionContext& ctx) const;
|
||||||
void process_unary_op(std::ostream& output_file, const UnaryOp& op, const InstructionContext& ctx) const;
|
void process_unary_op(std::ostream& output_file, const UnaryOp& op, const InstructionContext& ctx) const;
|
||||||
|
void process_store_op(std::ostream& output_file, const StoreOp& op, const InstructionContext& ctx) const;
|
||||||
void emit_branch_condition(std::ostream& output_file, const ConditionalBranchOp& op, const InstructionContext& ctx) const;
|
void emit_branch_condition(std::ostream& output_file, const ConditionalBranchOp& op, const InstructionContext& ctx) const;
|
||||||
void emit_branch_close(std::ostream& output_file) const;
|
void emit_branch_close(std::ostream& output_file) const;
|
||||||
void emit_check_fr(std::ostream& output_file, int fpr) const;
|
void emit_check_fr(std::ostream& output_file, int fpr) const;
|
||||||
|
@ -780,6 +812,84 @@ void CGenerator::process_unary_op(std::ostream& output_file, const UnaryOp& op,
|
||||||
fmt::print(output_file, "{} = {};\n", output, input);
|
fmt::print(output_file, "{} = {};\n", output, input);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CGenerator::process_store_op(std::ostream& output_file, const StoreOp& op, const InstructionContext& ctx) const {
|
||||||
|
// Thread local variables to prevent allocations when possible.
|
||||||
|
// TODO these thread locals probably don't actually help right now, so figure out a better way to prevent allocations.
|
||||||
|
thread_local std::string base_str{};
|
||||||
|
thread_local std::string imm_str{};
|
||||||
|
thread_local std::string value_input{};
|
||||||
|
bool is_infix;
|
||||||
|
get_operand_string(Operand::Base, UnaryOpType::None, ctx, base_str);
|
||||||
|
get_operand_string(Operand::ImmS16, UnaryOpType::None, ctx, imm_str);
|
||||||
|
get_operand_string(op.value_input, UnaryOpType::None, ctx, value_input);
|
||||||
|
|
||||||
|
enum class StoreSyntax {
|
||||||
|
Func,
|
||||||
|
FuncWithRdram,
|
||||||
|
Assignment,
|
||||||
|
};
|
||||||
|
|
||||||
|
StoreSyntax syntax;
|
||||||
|
std::string func_text;
|
||||||
|
|
||||||
|
switch (op.type) {
|
||||||
|
case StoreOpType::SD:
|
||||||
|
func_text = "SD";
|
||||||
|
syntax = StoreSyntax::Func;
|
||||||
|
break;
|
||||||
|
case StoreOpType::SDL:
|
||||||
|
func_text = "do_sdl";
|
||||||
|
syntax = StoreSyntax::FuncWithRdram;
|
||||||
|
break;
|
||||||
|
case StoreOpType::SDR:
|
||||||
|
func_text = "do_sdr";
|
||||||
|
syntax = StoreSyntax::FuncWithRdram;
|
||||||
|
break;
|
||||||
|
case StoreOpType::SW:
|
||||||
|
func_text = "MEM_W";
|
||||||
|
syntax = StoreSyntax::Assignment;
|
||||||
|
break;
|
||||||
|
case StoreOpType::SWL:
|
||||||
|
func_text = "do_swl";
|
||||||
|
syntax = StoreSyntax::FuncWithRdram;
|
||||||
|
break;
|
||||||
|
case StoreOpType::SWR:
|
||||||
|
func_text = "do_swr";
|
||||||
|
syntax = StoreSyntax::FuncWithRdram;
|
||||||
|
break;
|
||||||
|
case StoreOpType::SH:
|
||||||
|
func_text = "MEM_H";
|
||||||
|
syntax = StoreSyntax::Assignment;
|
||||||
|
break;
|
||||||
|
case StoreOpType::SB:
|
||||||
|
func_text = "MEM_B";
|
||||||
|
syntax = StoreSyntax::Assignment;
|
||||||
|
break;
|
||||||
|
case StoreOpType::SDC1:
|
||||||
|
func_text = "SD";
|
||||||
|
syntax = StoreSyntax::Func;
|
||||||
|
break;
|
||||||
|
case StoreOpType::SWC1:
|
||||||
|
func_text = "MEM_W";
|
||||||
|
syntax = StoreSyntax::Assignment;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw std::runtime_error("Unhandled store op");
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (syntax) {
|
||||||
|
case StoreSyntax::Func:
|
||||||
|
fmt::print(output_file, "{}({}, {}, {});\n", func_text, value_input, imm_str, base_str);
|
||||||
|
break;
|
||||||
|
case StoreSyntax::FuncWithRdram:
|
||||||
|
fmt::print(output_file, "{}(rdram, {}, {}, {});\n", func_text, imm_str, base_str, value_input);
|
||||||
|
break;
|
||||||
|
case StoreSyntax::Assignment:
|
||||||
|
fmt::print(output_file, "{}({}, {}) = {};\n", func_text, imm_str, base_str, value_input);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Major TODO, this function grew very organically and needs to be cleaned up. Ideally, it'll get split up into some sort of lookup table grouped by similar instruction types.
|
// Major TODO, this function grew very organically and needs to be cleaned up. Ideally, it'll get split up into some sort of lookup table grouped by similar instruction types.
|
||||||
bool process_instruction(const RecompPort::Context& context, const RecompPort::Config& config, const RecompPort::Function& func, const RecompPort::FunctionStats& stats, const std::unordered_set<uint32_t>& skipped_insns, size_t instr_index, const std::vector<rabbitizer::InstructionCpu>& instructions, std::ofstream& output_file, bool indent, bool emit_link_branch, int link_branch_index, size_t reloc_index, bool& needs_link_branch, bool& is_branch_likely, std::span<std::vector<uint32_t>> static_funcs_out) {
|
bool process_instruction(const RecompPort::Context& context, const RecompPort::Config& config, const RecompPort::Function& func, const RecompPort::FunctionStats& stats, const std::unordered_set<uint32_t>& skipped_insns, size_t instr_index, const std::vector<rabbitizer::InstructionCpu>& instructions, std::ofstream& output_file, bool indent, bool emit_link_branch, int link_branch_index, size_t reloc_index, bool& needs_link_branch, bool& is_branch_likely, std::span<std::vector<uint32_t>> static_funcs_out) {
|
||||||
const auto& section = context.sections[func.section_index];
|
const auto& section = context.sections[func.section_index];
|
||||||
|
@ -813,8 +923,6 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::C
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool at_reloc = false;
|
|
||||||
bool reloc_handled = false;
|
|
||||||
RecompPort::RelocType reloc_type = RecompPort::RelocType::R_MIPS_NONE;
|
RecompPort::RelocType reloc_type = RecompPort::RelocType::R_MIPS_NONE;
|
||||||
uint32_t reloc_section = 0;
|
uint32_t reloc_section = 0;
|
||||||
uint32_t reloc_target_section_offset = 0;
|
uint32_t reloc_target_section_offset = 0;
|
||||||
|
@ -844,8 +952,6 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::C
|
||||||
reloc_target_section_offset = reloc.section_offset;
|
reloc_target_section_offset = reloc.section_offset;
|
||||||
// Ignore all relocs that aren't HI16 or LO16.
|
// Ignore all relocs that aren't HI16 or LO16.
|
||||||
if (reloc_type == RecompPort::RelocType::R_MIPS_HI16 || reloc_type == RecompPort::RelocType::R_MIPS_LO16 || reloc_type == RecompPort::RelocType::R_MIPS_26) {
|
if (reloc_type == RecompPort::RelocType::R_MIPS_HI16 || reloc_type == RecompPort::RelocType::R_MIPS_LO16 || reloc_type == RecompPort::RelocType::R_MIPS_26) {
|
||||||
at_reloc = true;
|
|
||||||
|
|
||||||
if (reloc.reference_symbol) {
|
if (reloc.reference_symbol) {
|
||||||
reloc_reference_symbol = reloc.symbol_index;
|
reloc_reference_symbol = reloc.symbol_index;
|
||||||
static RecompPort::ReferenceSection dummy_section{
|
static RecompPort::ReferenceSection dummy_section{
|
||||||
|
@ -855,8 +961,8 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::C
|
||||||
.relocatable = false
|
.relocatable = false
|
||||||
};
|
};
|
||||||
const auto& reloc_reference_section = reloc.target_section == RecompPort::SectionAbsolute ? dummy_section : context.reference_sections[reloc.target_section];
|
const auto& reloc_reference_section = reloc.target_section == RecompPort::SectionAbsolute ? dummy_section : context.reference_sections[reloc.target_section];
|
||||||
if (!reloc_reference_section.relocatable) {
|
// Resolve HI16 and LO16 reference symbol relocs to non-relocatable sections by patching the instruction immediate.
|
||||||
at_reloc = false;
|
if (!reloc_reference_section.relocatable && (reloc_type == RecompPort::RelocType::R_MIPS_HI16 || reloc_type == RecompPort::RelocType::R_MIPS_LO16)) {
|
||||||
uint32_t full_immediate = reloc.section_offset + reloc_reference_section.ram_addr;
|
uint32_t full_immediate = reloc.section_offset + reloc_reference_section.ram_addr;
|
||||||
|
|
||||||
if (reloc_type == RecompPort::RelocType::R_MIPS_HI16) {
|
if (reloc_type == RecompPort::RelocType::R_MIPS_HI16) {
|
||||||
|
@ -865,6 +971,10 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::C
|
||||||
else if (reloc_type == RecompPort::RelocType::R_MIPS_LO16) {
|
else if (reloc_type == RecompPort::RelocType::R_MIPS_LO16) {
|
||||||
imm = full_immediate & 0xFFFF;
|
imm = full_immediate & 0xFFFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The reloc has been processed, so delete it to none to prevent it getting processed a second time during instruction code generation.
|
||||||
|
reloc_type = RecompPort::RelocType::R_MIPS_NONE;
|
||||||
|
reloc_reference_symbol = (size_t)-1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -884,11 +994,6 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::C
|
||||||
fmt::print(output_file, ";\n");
|
fmt::print(output_file, ";\n");
|
||||||
};
|
};
|
||||||
|
|
||||||
auto print_branch_condition = [&]<typename... Ts>(fmt::format_string<Ts...> fmt_str, Ts ...args) {
|
|
||||||
fmt::vprint(output_file, fmt_str, fmt::make_format_args(args...));
|
|
||||||
fmt::print(output_file, " ");
|
|
||||||
};
|
|
||||||
|
|
||||||
auto print_unconditional_branch = [&]<typename... Ts>(fmt::format_string<Ts...> fmt_str, Ts ...args) {
|
auto print_unconditional_branch = [&]<typename... Ts>(fmt::format_string<Ts...> fmt_str, Ts ...args) {
|
||||||
if (instr_index < instructions.size() - 1) {
|
if (instr_index < instructions.size() - 1) {
|
||||||
bool dummy_needs_link_branch;
|
bool dummy_needs_link_branch;
|
||||||
|
@ -1051,33 +1156,6 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::C
|
||||||
|
|
||||||
int cop1_cs = (int)instr.Get_cop1cs();
|
int cop1_cs = (int)instr.Get_cop1cs();
|
||||||
|
|
||||||
std::string unsigned_imm_string;
|
|
||||||
std::string signed_imm_string;
|
|
||||||
|
|
||||||
if (!at_reloc) {
|
|
||||||
unsigned_imm_string = fmt::format("{:#X}", imm);
|
|
||||||
signed_imm_string = fmt::format("{:#X}", (int16_t)imm);
|
|
||||||
} else {
|
|
||||||
switch (reloc_type) {
|
|
||||||
case RecompPort::RelocType::R_MIPS_HI16:
|
|
||||||
unsigned_imm_string = fmt::format("RELOC_HI16({}, {:#X})", reloc_section, reloc_target_section_offset);
|
|
||||||
signed_imm_string = "(int16_t)" + unsigned_imm_string;
|
|
||||||
reloc_handled = true;
|
|
||||||
break;
|
|
||||||
case RecompPort::RelocType::R_MIPS_LO16:
|
|
||||||
unsigned_imm_string = fmt::format("RELOC_LO16({}, {:#X})", reloc_section, reloc_target_section_offset);
|
|
||||||
signed_imm_string = "(int16_t)" + unsigned_imm_string;
|
|
||||||
reloc_handled = true;
|
|
||||||
break;
|
|
||||||
case RecompPort::RelocType::R_MIPS_26:
|
|
||||||
// Nothing to do here, this will be handled by print_func_call.
|
|
||||||
reloc_handled = true;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw std::runtime_error(fmt::format("Unexpected reloc type {} in {}\n", static_cast<int>(reloc_type), func.name));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool handled = true;
|
bool handled = true;
|
||||||
|
|
||||||
switch (instr.getUniqueId()) {
|
switch (instr.getUniqueId()) {
|
||||||
|
@ -1152,26 +1230,6 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::C
|
||||||
case InstrId::cpu_ddivu:
|
case InstrId::cpu_ddivu:
|
||||||
print_line("DDIVU(U64({}{}), U64({}{}), &lo, &hi)", ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt);
|
print_line("DDIVU(U64({}{}), U64({}{}), &lo, &hi)", ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt);
|
||||||
break;
|
break;
|
||||||
// Stores
|
|
||||||
case InstrId::cpu_sd:
|
|
||||||
print_line("SD({}{}, {}, {}{})", ctx_gpr_prefix(rt), rt, signed_imm_string, ctx_gpr_prefix(base), base);
|
|
||||||
break;
|
|
||||||
case InstrId::cpu_sw:
|
|
||||||
print_line("MEM_W({}, {}{}) = {}{}", signed_imm_string, ctx_gpr_prefix(base), base, ctx_gpr_prefix(rt), rt);
|
|
||||||
break;
|
|
||||||
case InstrId::cpu_sh:
|
|
||||||
print_line("MEM_H({}, {}{}) = {}{}", signed_imm_string, ctx_gpr_prefix(base), base, ctx_gpr_prefix(rt), rt);
|
|
||||||
break;
|
|
||||||
case InstrId::cpu_sb:
|
|
||||||
print_line("MEM_B({}, {}{}) = {}{}", signed_imm_string, ctx_gpr_prefix(base), base, ctx_gpr_prefix(rt), rt);
|
|
||||||
break;
|
|
||||||
case InstrId::cpu_swl:
|
|
||||||
print_line("do_swl(rdram, {}, {}{}, {}{})", signed_imm_string, ctx_gpr_prefix(base), base, ctx_gpr_prefix(rt), rt);
|
|
||||||
break;
|
|
||||||
case InstrId::cpu_swr:
|
|
||||||
print_line("do_swr(rdram, {}, {}{}, {}{})", signed_imm_string, ctx_gpr_prefix(base), base, ctx_gpr_prefix(rt), rt);
|
|
||||||
break;
|
|
||||||
|
|
||||||
// Branches
|
// Branches
|
||||||
case InstrId::cpu_jal:
|
case InstrId::cpu_jal:
|
||||||
print_func_call(instr.getBranchVramGeneric());
|
print_func_call(instr.getBranchVramGeneric());
|
||||||
|
@ -1282,21 +1340,6 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::C
|
||||||
print_line("do_break({})", instr_vram);
|
print_line("do_break({})", instr_vram);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Cop1 stores
|
|
||||||
case InstrId::cpu_swc1:
|
|
||||||
if ((ft & 1) == 0) {
|
|
||||||
// even fpr
|
|
||||||
print_line("MEM_W({}, {}{}) = ctx->f{}.u32l", signed_imm_string, ctx_gpr_prefix(base), base, ft);
|
|
||||||
} else {
|
|
||||||
// odd fpr
|
|
||||||
print_line("MEM_W({}, {}{}) = ctx->f_odd[({} - 1) * 2]", signed_imm_string, ctx_gpr_prefix(base), base, ft);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case InstrId::cpu_sdc1:
|
|
||||||
print_line("CHECK_FR(ctx, {})", ft);
|
|
||||||
print_line("SD(ctx->f{}.u64, {}, {}{})", ft, signed_imm_string, ctx_gpr_prefix(base), base);
|
|
||||||
break;
|
|
||||||
|
|
||||||
// Cop1 rounding mode
|
// Cop1 rounding mode
|
||||||
case InstrId::cpu_ctc1:
|
case InstrId::cpu_ctc1:
|
||||||
if (cop1_cs != 31) {
|
if (cop1_cs != 31) {
|
||||||
|
@ -1329,7 +1372,7 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::C
|
||||||
instruction_context.cop1_cs = cop1_cs;
|
instruction_context.cop1_cs = cop1_cs;
|
||||||
instruction_context.imm16 = imm;
|
instruction_context.imm16 = imm;
|
||||||
instruction_context.reloc_type = reloc_type;
|
instruction_context.reloc_type = reloc_type;
|
||||||
instruction_context.reloc_section_index = func.section_index; // TODO allow relocs to other sections?
|
instruction_context.reloc_section_index = reloc_section;
|
||||||
instruction_context.reloc_target_section_offset = reloc_target_section_offset;
|
instruction_context.reloc_target_section_offset = reloc_target_section_offset;
|
||||||
|
|
||||||
auto do_check_fr = [](std::ostream& output_file, const CGenerator& generator, const InstructionContext& ctx, Operand operand) {
|
auto do_check_fr = [](std::ostream& output_file, const CGenerator& generator, const InstructionContext& ctx, Operand operand) {
|
||||||
|
@ -1440,6 +1483,19 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::C
|
||||||
handled = true;
|
handled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto find_store_it = store_ops.find(instr.getUniqueId());
|
||||||
|
if (find_store_it != store_ops.end()) {
|
||||||
|
print_indent();
|
||||||
|
const StoreOp& op = find_store_it->second;
|
||||||
|
|
||||||
|
if (op.type == StoreOpType::SDC1) {
|
||||||
|
do_check_fr(output_file, generator, instruction_context, op.value_input);
|
||||||
|
}
|
||||||
|
|
||||||
|
generator.process_store_op(output_file, op, instruction_context);
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (!handled) {
|
if (!handled) {
|
||||||
fmt::print(stderr, "Unhandled instruction: {}\n", instr.getOpcodeName());
|
fmt::print(stderr, "Unhandled instruction: {}\n", instr.getOpcodeName());
|
||||||
return false;
|
return false;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue