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 "";
|
||||
}
|
||||
|
||||
enum class StoreOpType {
|
||||
SD,
|
||||
SDL,
|
||||
SDR,
|
||||
SW,
|
||||
SWL,
|
||||
SWR,
|
||||
SH,
|
||||
SB,
|
||||
SDC1,
|
||||
SWC1
|
||||
};
|
||||
|
||||
enum class UnaryOpType {
|
||||
None,
|
||||
ToS32,
|
||||
|
@ -143,6 +156,11 @@ enum class Operand {
|
|||
Base = Rs, // Alias for Rs for loads
|
||||
};
|
||||
|
||||
struct StoreOp {
|
||||
StoreOpType type;
|
||||
Operand value_input;
|
||||
};
|
||||
|
||||
struct UnaryOp {
|
||||
UnaryOpType operation;
|
||||
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 }},
|
||||
};
|
||||
|
||||
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 {
|
||||
int rd;
|
||||
int rs;
|
||||
|
@ -372,6 +403,7 @@ public:
|
|||
CGenerator() = default;
|
||||
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_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_close(std::ostream& output_file) 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);
|
||||
}
|
||||
|
||||
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.
|
||||
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];
|
||||
|
@ -813,8 +923,6 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::C
|
|||
return true;
|
||||
}
|
||||
|
||||
bool at_reloc = false;
|
||||
bool reloc_handled = false;
|
||||
RecompPort::RelocType reloc_type = RecompPort::RelocType::R_MIPS_NONE;
|
||||
uint32_t reloc_section = 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;
|
||||
// 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) {
|
||||
at_reloc = true;
|
||||
|
||||
if (reloc.reference_symbol) {
|
||||
reloc_reference_symbol = reloc.symbol_index;
|
||||
static RecompPort::ReferenceSection dummy_section{
|
||||
|
@ -855,8 +961,8 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::C
|
|||
.relocatable = false
|
||||
};
|
||||
const auto& reloc_reference_section = reloc.target_section == RecompPort::SectionAbsolute ? dummy_section : context.reference_sections[reloc.target_section];
|
||||
if (!reloc_reference_section.relocatable) {
|
||||
at_reloc = false;
|
||||
// Resolve HI16 and LO16 reference symbol relocs to non-relocatable sections by patching the instruction immediate.
|
||||
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;
|
||||
|
||||
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) {
|
||||
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");
|
||||
};
|
||||
|
||||
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) {
|
||||
if (instr_index < instructions.size() - 1) {
|
||||
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();
|
||||
|
||||
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;
|
||||
|
||||
switch (instr.getUniqueId()) {
|
||||
|
@ -1152,26 +1230,6 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::C
|
|||
case InstrId::cpu_ddivu:
|
||||
print_line("DDIVU(U64({}{}), U64({}{}), &lo, &hi)", ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt);
|
||||
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
|
||||
case InstrId::cpu_jal:
|
||||
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);
|
||||
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
|
||||
case InstrId::cpu_ctc1:
|
||||
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.imm16 = imm;
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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) {
|
||||
fmt::print(stderr, "Unhandled instruction: {}\n", instr.getOpcodeName());
|
||||
return false;
|
||||
|
|
Loading…
Add table
Reference in a new issue