Implemented initial set of instructions and ignored functions

This commit is contained in:
Mr-Wiseguy 2022-11-15 19:55:48 -05:00
parent 4b1dc14019
commit 8a0f0da0cc
9 changed files with 1204 additions and 15 deletions

3
.gitignore vendored
View file

@ -4,6 +4,9 @@
# Input elf files
*.elf
# Output C files
out/
# Linux build output
build/
*.o

View file

@ -22,6 +22,7 @@
<VCProjectVersion>16.0</VCProjectVersion>
<ProjectGuid>{23C26E84-DC01-43A6-B11B-0B4A2D79A5DD}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
@ -75,8 +76,9 @@
<WarningLevel>Level3</WarningLevel>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<Optimization>Disabled</Optimization>
<LanguageStandard>stdcpp17</LanguageStandard>
<AdditionalIncludeDirectories>$(SolutionDir)lib\rabbitizer\include;$(SolutionDir)lib\rabbitizer\cplusplus\include;$(SolutionDir)lib\ELFIO;$(SolutionDir)lib\fmt\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<LanguageStandard>stdcpp20</LanguageStandard>
<AdditionalIncludeDirectories>$(SolutionDir)lib\rabbitizer\include;$(SolutionDir)lib\rabbitizer\cplusplus\include;$(SolutionDir)lib\ELFIO;$(SolutionDir)lib\fmt\include;$(ProjectDir)include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<OpenMPSupport>true</OpenMPSupport>
</ClCompile>
<Link>
<TargetMachine>MachineX86</TargetMachine>
@ -91,8 +93,9 @@
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
<WarningLevel>Level3</WarningLevel>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<LanguageStandard>stdcpp17</LanguageStandard>
<AdditionalIncludeDirectories>$(SolutionDir)lib\rabbitizer\include;$(SolutionDir)lib\rabbitizer\cplusplus\include;$(SolutionDir)lib\ELFIO;$(SolutionDir)lib\fmt\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<LanguageStandard>stdcpp20</LanguageStandard>
<AdditionalIncludeDirectories>$(SolutionDir)lib\rabbitizer\include;$(SolutionDir)lib\rabbitizer\cplusplus\include;$(SolutionDir)lib\ELFIO;$(SolutionDir)lib\fmt\include;$(ProjectDir)include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<OpenMPSupport>true</OpenMPSupport>
</ClCompile>
<Link>
<TargetMachine>MachineX86</TargetMachine>
@ -105,8 +108,9 @@
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<LanguageStandard>stdcpp17</LanguageStandard>
<AdditionalIncludeDirectories>$(SolutionDir)lib\rabbitizer\include;$(SolutionDir)lib\rabbitizer\cplusplus\include;$(SolutionDir)lib\ELFIO;$(SolutionDir)lib\fmt\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<LanguageStandard>stdcpp20</LanguageStandard>
<AdditionalIncludeDirectories>$(SolutionDir)lib\rabbitizer\include;$(SolutionDir)lib\rabbitizer\cplusplus\include;$(SolutionDir)lib\ELFIO;$(SolutionDir)lib\fmt\include;$(ProjectDir)include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<OpenMPSupport>true</OpenMPSupport>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
@ -115,8 +119,9 @@
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<LanguageStandard>stdcpp17</LanguageStandard>
<AdditionalIncludeDirectories>$(SolutionDir)lib\rabbitizer\include;$(SolutionDir)lib\rabbitizer\cplusplus\include;$(SolutionDir)lib\ELFIO;$(SolutionDir)lib\fmt\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<LanguageStandard>stdcpp20</LanguageStandard>
<AdditionalIncludeDirectories>$(SolutionDir)lib\rabbitizer\include;$(SolutionDir)lib\rabbitizer\cplusplus\include;$(SolutionDir)lib\ELFIO;$(SolutionDir)lib\fmt\include;$(ProjectDir)include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<OpenMPSupport>true</OpenMPSupport>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
@ -133,8 +138,10 @@
</ItemGroup>
<ItemGroup>
<ClCompile Include="src\main.cpp" />
<ClCompile Include="src\recompilation.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="include\recomp_port.h" />
<ClInclude Include="lib\ELFIO\elfio\elfio.hpp" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />

View file

@ -18,10 +18,16 @@
<ClCompile Include="src\main.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\recompilation.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="lib\ELFIO\elfio\elfio.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="include\recomp_port.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
</Project>

31
include/recomp_port.h Normal file
View file

@ -0,0 +1,31 @@
#ifndef __RECOMP_PORT__
#define __RECOMP_PORT__
#include <span>
#include <string_view>
#include <cstdint>
#ifdef _MSC_VER
inline uint32_t byteswap(uint32_t val) {
return _byteswap_ulong(val);
}
#else
constexpr uint32_t byteswap(uint32_t val) {
return __builtin_bswap32(val);
}
#endif
namespace RecompPort {
struct Function {
uint32_t vram;
uint32_t rom;
const std::span<const uint32_t> words;
std::string name;
};
bool recompile_function(const Function& func, std::string_view output_path);
}
#endif

View file

@ -133,15 +133,23 @@
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<LinkIncremental>true</LinkIncremental>
<OutDir>$(SolutionDir)$(Configuration)\rabbitizer_build\</OutDir>
<IntDir>$(Configuration)\rabbitizer_build\</IntDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<LinkIncremental>false</LinkIncremental>
<OutDir>$(SolutionDir)$(Configuration)\rabbitizer_build\</OutDir>
<IntDir>$(Configuration)\rabbitizer_build\</IntDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<LinkIncremental>true</LinkIncremental>
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\rabbitizer_build\</OutDir>
<IntDir>$(Platform)\$(Configuration)\rabbitizer_build\</IntDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<LinkIncremental>false</LinkIncremental>
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\rabbitizer_build\</OutDir>
<IntDir>$(Platform)\$(Configuration)\rabbitizer_build\</IntDir>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<Link>

View file

@ -72,15 +72,23 @@
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<LinkIncremental>true</LinkIncremental>
<OutDir>$(SolutionDir)$(Configuration)\fmtlib_build\</OutDir>
<IntDir>$(Configuration)\fmtlib_build\</IntDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<LinkIncremental>false</LinkIncremental>
<OutDir>$(SolutionDir)$(Configuration)\fmtlib_build\</OutDir>
<IntDir>$(Configuration)\fmtlib_build\</IntDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<LinkIncremental>true</LinkIncremental>
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\fmtlib_build\</OutDir>
<IntDir>$(Platform)\$(Configuration)\fmtlib_build\</IntDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<LinkIncremental>false</LinkIncremental>
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\fmtlib_build\</OutDir>
<IntDir>$(Platform)\$(Configuration)\fmtlib_build\</IntDir>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>

94
recomp.h Normal file
View file

@ -0,0 +1,94 @@
#ifndef __RECOMP_H__
#define __RECOMP_H__
#include <stdint.h>
#define ADD32(a, b) \
((uint64_t)(int32_t)((a) + (b)))
#define SUB32(a, b) \
((uint64_t)(int32_t)((a) - (b)))
#define MEM_D(offset, reg) \
(*(int64_t*)((rdram) + (((reg) + (offset)) ^ 3)))
#define MEM_W(offset, reg) \
(*(int32_t*)((rdram) + (((reg) + (offset)) ^ 3)))
#define MEM_H(offset, reg) \
(*(int16_t*)((rdram) + (((reg) + (offset)) ^ 3)))
#define MEM_B(offset, reg) \
(*(int8_t*)((rdram) + (((reg) + (offset)) ^ 3)))
#define MEM_HU(offset, reg) \
(*(uint16_t*)((rdram) + (((reg) + (offset)) ^ 3)))
#define MEM_BU(offset, reg) \
(*(uint8_t*)((rdram) + (((reg) + (offset)) ^ 3)))
#define S32(val) \
((int32_t)(val))
#define U32(val) \
((uint32_t)(val))
#define S64(val) \
((int64_t)(val))
#define MUL_S(val1, val2) \
((val1) * (val2))
#define MUL_D(val1, val2) \
((val1) * (val2))
#define DIV_S(val1, val2) \
((val1) / (val2))
#define DIV_D(val1, val2) \
((val1) / (val2))
#define CVT_S_W(val) \
((float)((int32_t)(val)))
#define CVT_D_W(val) \
((double)((int32_t)(val)))
#define CVT_D_S(val) \
((double)(val))
#define CVT_S_D(val) \
((float)(val))
#define TRUNC_W_S(val) \
((int32_t)(val))
#define TRUNC_W_D(val) \
((int32_t)(val))
typedef uint64_t gpr;
typedef union {
double d;
struct {
float fl;
float fh;
};
struct {
uint32_t u32l;
uint32_t u32h;
};
uint64_t u64;
} fpr;
typedef struct {
gpr r0, r1, r2, r3, r4, r5, r6, r7,
r8, r9, r10, r11, r12, r13, r14, r15,
r16, r17, r18, r19, r20, r21, r22, r23,
r24, r25, r26, r27, r28, r29, r30, r31;
fpr f0, f2, f4, f6, f8, f10, f12, f14,
f16, f18, f20, f22, f24, f26, f28, f30;
uint64_t hi, lo;
} recomp_context;
#endif

View file

@ -1,17 +1,330 @@
#include <cstdio>
#include <cstdlib>
#include <unordered_set>
#include <span>
#include "rabbitizer.hpp"
#include "elfio/elfio.hpp"
#include "fmt/format.h"
#include <cstdio>
#include "recomp_port.h"
std::unordered_set<std::string> ignored_funcs {
// OS initialize functions
"__createSpeedParam",
"__osInitialize_common",
"__osInitialize_autodetect",
"osInitialize",
// Audio interface functions
"osAiGetLength",
"osAiGetStatus",
"osAiSetFrequency",
"osAiSetNextBuffer",
"__osAiDeviceBusy",
// Video interface functions
"osViBlack",
"osViFade",
"osViGetCurrentField",
"osViGetCurrentFramebuffer",
"osViGetCurrentLine",
"osViGetCurrentMode",
"osViGetNextFramebuffer",
"osViGetStatus",
"osViRepeatLine",
"osViSetEvent",
"osViSetMode",
"osViSetSpecialFeatures",
"osViSetXScale",
"osViSetYScale",
"osViSwapBuffer",
"osCreateViManager",
"viMgrMain",
"__osViInit",
"__osViSwapContext",
"__osViGetCurrentContext",
// RDP functions
"osDpGetCounters",
"osDpSetStatus",
"osDpGetStatus",
"osDpSetNextBuffer",
"__osDpDeviceBusy",
// RSP functions
"osSpTaskLoad",
"osSpTaskStartGo",
"osSpTaskYield",
"osSpTaskYielded",
"__osSpDeviceBusy",
"__osSpGetStatus",
"__osSpRawStartDma",
"__osSpRawReadIo",
"__osSpRawWriteIo",
"__osSpSetPc",
"__osSpSetStatus",
// Controller functions
"osContGetQuery",
"osContGetReadData",
"osContInit",
"osContReset",
"osContSetCh",
"osContStartQuery",
"osContStartReadData",
"__osContAddressCrc",
"__osContDataCrc",
"__osContGetInitData",
"__osContRamRead",
"__osContRamWrite",
// EEPROM functions
"osEepromLongRead",
"osEepromLongWrite",
"osEepromProbe",
"osEepromRead",
"osEepromWrite",
"__osEepStatus",
// Rumble functions
"osMotorInit",
"osMotorStart",
"osMotorStop",
// PFS functions
"osPfsAllocateFile",
"osPfsChecker",
"osPfsDeleteFile",
"osPfsFileState",
"osPfsFindFile",
"osPfsFreeBlocks",
"osPfsGetLabel",
"osPfsInit",
"osPfsInitPak",
"osPfsIsPlug",
"osPfsNumFiles",
"osPfsRepairId",
"osPfsReadWriteFile",
"__osPackEepReadData",
"__osPackEepWriteData",
"__osPackRamReadData",
"__osPackRamWriteData",
"__osPackReadData",
"__osPackRequestData",
"__osPfsGetInitData",
"__osPfsGetOneChannelData",
"__osPfsGetStatus",
"__osPfsRequestData",
"__osPfsRequestOneChannel",
"__osPfsCreateAccessQueue",
// Low level serial interface functions
"__osSiDeviceBusy",
"__osSiGetStatus",
"__osSiRawStartDma",
"__osSiRawReadIo",
"__osSiRawWriteIo",
"__osSiCreateAccessQueue",
"__osSiGetAccess",
"__osSiRelAccess",
// Parallel interface (cartridge, DMA, etc.) functions
"osCartRomInit",
"osLeoDiskInit",
"osCreatePiManager",
"__osDevMgrMain",
"osPiGetCmdQueue",
"osPiGetStatus",
"osPiReadIo",
"osPiStartDma",
"osPiWriteIo",
"osEPiGetDeviceType",
"osEPiStartDma",
"osEPiWriteIo",
"osEPiReadIo",
"osPiRawStartDma",
"osPiRawReadIo",
"osPiRawWriteIo",
"osEPiRawStartDma",
"osEPiRawReadIo",
"osEPiRawWriteIo",
"__osPiRawStartDma",
"__osPiRawReadIo",
"__osPiRawWriteIo",
"__osEPiRawStartDma",
"__osEPiRawReadIo",
"__osEPiRawWriteIo",
"__osPiDeviceBusy",
"__osPiCreateAccessQueue",
"__osPiGetAccess",
"__osPiRelAccess",
"__osLeoAbnormalResume",
"__osLeoInterrupt",
"__osLeoResume",
// Threading functions
"osCreateThread",
"osStartThread",
"osStopThread",
"osDestroyThread",
"osYieldThread",
"osSetThreadPri",
"osGetThreadPri",
"osGetThreadId",
"__osDequeueThread",
// Message Queue functions
"osCreateMesgQueue",
"osSendMesg",
"osJamMesg",
"osRecvMesg",
"osSetEventMesg",
// Timer functions
"osStartTimer",
"osSetTimer",
"osStopTimer",
"__osInsertTimer",
"__osTimerInterrupt",
"__osTimerServicesInit",
"__osSetTimerIntr",
// exceptasm functions
"__osExceptionPreamble",
"__osException",
"send_mesg",
"handle_CpU",
"__osEnqueueAndYield",
"__osEnqueueThread",
"__osPopThread",
"__osNop",
"__osDispatchThread",
"__osCleanupThread",
"osGetCurrFaultedThread",
"osGetNextFaultedThread",
// interrupt functions
"osSetIntMask",
"osGetIntMask",
"__osDisableInt",
"__osRestoreInt",
"__osSetGlobalIntMask",
"__osResetGlobalIntMask",
// TLB functions
"osMapTLB",
"osUnmapTLB",
"osUnmapTLBAll",
"osSetTLBASID",
"osMapTLBRdb",
"osVirtualToPhysical",
"__osGetTLBHi",
"__osGetTLBLo0",
"__osGetTLBLo1",
"__osGetTLBPageMask",
"__osGetTLBASID",
"__osProbeTLB",
// Coprocessor 0 functions
"__osSetCount",
"osGetCount",
"__osSetSR",
"__osGetSR",
"__osSetCause",
"__osGetCause",
"__osSetCompare",
"__osGetCompare",
"__osSetConfig",
"__osGetConfig",
"__osSetWatchLo",
"__osGetWatchLo",
};
int main(int argc, char** argv) {
uint32_t word = 0x8D4A7E18; // lw
uint32_t vram = 0x80000000;
int extraLJust = 5;
rabbitizer::InstructionCpu instr(word, vram);
if (argc != 2) {
fmt::print("Usage: {} [input elf file]\n", argv[0]);
std::exit(EXIT_SUCCESS);
}
fmt::print("{}\n", instr.isBranch());
fmt::print("{:08X}: {}\n", word, instr.disassemble(extraLJust));
ELFIO::elfio elf_file;
auto exit_failure = [] (const std::string& error_str) {
fmt::print(stderr, error_str);
std::exit(EXIT_FAILURE);
};
if (!elf_file.load(argv[1])) {
exit_failure("Failed to load provided elf file\n");
}
if (elf_file.get_class() != ELFIO::ELFCLASS32) {
exit_failure("Incorrect elf class\n");
}
if (elf_file.get_encoding() != ELFIO::ELFDATA2MSB) {
exit_failure("Incorrect endianness\n");
}
// Pointer to the symbol table section
ELFIO::section* symtab_section = nullptr;
// Size of the ROM as determined by the elf
ELFIO::Elf_Xword rom_size = 0;
// ROM address of each section
std::vector<ELFIO::Elf_Xword> section_rom_addrs{};
section_rom_addrs.resize(elf_file.sections.size());
// Iterate over every section to record rom addresses and find the symbol table
fmt::print("Sections\n");
for (const std::unique_ptr<ELFIO::section>& section : elf_file.sections) {
fmt::print(" {}: {} @ 0x{:08X}, 0x{:08X}\n", section->get_index(), section->get_name(), section->get_address(), rom_size);
// Set the rom address of this section to the current accumulated ROM size
section_rom_addrs[section->get_index()] = rom_size;
// If this section isn't bss (SHT_NOBITS) and ends up in the rom (SHF_ALLOC), increase the rom size by this section's size
if (section->get_type() != ELFIO::SHT_NOBITS && section->get_flags() & ELFIO::SHF_ALLOC) {
rom_size += section->get_size();
}
// Check if this section is the symbol table and record it if so
if (section->get_type() == ELFIO::SHT_SYMTAB) {
symtab_section = section.get();
}
}
// If no symbol table was found then exit
if (symtab_section == nullptr) {
exit_failure("No symbol section found\n");
}
ELFIO::symbol_section_accessor symbols{ elf_file, symtab_section };
fmt::print("Num symbols: {}\n", symbols.get_symbols_num());
std::vector<RecompPort::Function> functions{};
functions.reserve(1024);
for (int sym_index = 0; sym_index < symbols.get_symbols_num(); sym_index++) {
std::string name;
ELFIO::Elf64_Addr value;
ELFIO::Elf_Xword size;
unsigned char bind;
unsigned char type;
ELFIO::Elf_Half section_index;
unsigned char other;
// Read symbol properties
symbols.get_symbol(sym_index, name, value, size, bind, type,
section_index, other);
// Check if this symbol is a function
if (type == ELFIO::STT_FUNC) {
auto section_rom_addr = section_rom_addrs[section_index];
auto section_offset = value - elf_file.sections[section_index]->get_address();
const uint32_t* words = reinterpret_cast<const uint32_t*>(elf_file.sections[section_index]->get_data() + section_offset);
functions.emplace_back(
static_cast<uint32_t>(value),
static_cast<uint32_t>(section_offset + section_rom_addr),
std::span{ reinterpret_cast<const uint32_t*>(words), size / 4 },
std::move(name)
);
}
}
fmt::print("Function count: {}\n", functions.size());
//#pragma omp parallel for
for (size_t i = 0; i < functions.size(); i++) {
const auto& func = functions[i];
if (!ignored_funcs.contains(func.name)) {
if (RecompPort::recompile_function(func, "out/" + func.name + ".c") == false) {
fmt::print(stderr, "Error recompiling {}\n", func.name);
std::exit(EXIT_FAILURE);
}
}
}
//RecompPort::recompile_function(functions.back(), "test.c");
return 0;
}

719
src/recompilation.cpp Normal file
View file

@ -0,0 +1,719 @@
#include <vector>
#include <set>
#include "rabbitizer.hpp"
#include "fmt/format.h"
#include "fmt/ostream.h"
#include "recomp_port.h"
using InstrId = rabbitizer::InstrId::UniqueId;
std::string_view ctx_gpr_prefix(int reg) {
if (reg != 0) {
return "ctx->r";
}
return "";
}
bool process_instruction(size_t instr_index, const std::vector<rabbitizer::InstructionCpu>& instructions, std::ofstream& output_file, bool indent, bool emit_link_branch, int link_branch_index, bool& needs_link_branch, bool& is_branch_likely) {
const auto& instr = instructions[instr_index];
needs_link_branch = false;
is_branch_likely = false;
// Output a comment with the original instruction
if (instr.isBranch() || instr.getUniqueId() == InstrId::cpu_j) {
fmt::print(output_file, " // {}\n", instr.disassemble(0, fmt::format("L_{:08X}", (uint32_t)instr.getBranchVramGeneric())));
} else if (instr.getUniqueId() == InstrId::cpu_jal) {
fmt::print(output_file, " // {}\n", instr.disassemble(0, "func"));
} else {
fmt::print(output_file, " // {}\n", instr.disassemble(0));
}
auto print_indent = [&]() {
fmt::print(output_file, " ");
};
auto print_line = [&]<typename... Ts>(fmt::format_string<Ts...> fmt_str, Ts ...args) {
print_indent();
fmt::print(output_file, fmt_str, args...);
fmt::print(output_file, ";\n");
};
auto print_branch_condition = [&]<typename... Ts>(fmt::format_string<Ts...> fmt_str, Ts ...args) {
fmt::print(output_file, fmt_str, args...);
fmt::print(output_file, " ");
};
auto print_branch = [&]<typename... Ts>(fmt::format_string<Ts...> fmt_str, Ts ...args) {
fmt::print(output_file, "{{\n ");
if (instr_index < instructions.size() - 1) {
bool dummy_needs_link_branch;
bool dummy_is_branch_likely;
process_instruction(instr_index + 1, instructions, output_file, true, false, link_branch_index, dummy_needs_link_branch, dummy_is_branch_likely);
}
fmt::print(output_file, " ");
fmt::print(output_file, fmt_str, args...);
if (needs_link_branch) {
fmt::print(output_file, ";\n goto after_{}", link_branch_index);
}
fmt::print(output_file, ";\n }}\n");
};
if (indent) {
print_indent();
}
int rd = (int)instr.GetO32_rd();
int rs = (int)instr.GetO32_rs();
int base = rs;
int rt = (int)instr.GetO32_rt();
int sa = (int)instr.Get_sa();
int fd = (int)instr.GetO32_fd();
int fs = (int)instr.GetO32_fs();
int ft = (int)instr.GetO32_ft();
uint16_t imm = instr.Get_immediate();
switch (instr.getUniqueId()) {
case InstrId::cpu_nop:
fmt::print(output_file, "\n");
break;
// Arithmetic
case InstrId::cpu_lui:
print_line("{}{} = {:#X} << 16", ctx_gpr_prefix(rt), rt, imm);
break;
case InstrId::cpu_addu:
print_line("{}{} = ADD32({}{}, {}{})", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt);
break;
case InstrId::cpu_negu: // pseudo instruction for subu x, 0, y
case InstrId::cpu_subu:
print_line("{}{} = SUB32({}{}, {}{})", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt);
break;
case InstrId::cpu_addiu:
print_line("{}{} = ADD32({}{}, {:#X})", ctx_gpr_prefix(rt), rt, ctx_gpr_prefix(rs), rs, (int16_t)imm);
break;
case InstrId::cpu_and:
print_line("{}{} = {}{} & {}{}", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt);
break;
case InstrId::cpu_andi:
print_line("{}{} = {}{} & {:#X}", ctx_gpr_prefix(rt), rt, ctx_gpr_prefix(rs), rs, imm);
break;
case InstrId::cpu_or:
print_line("{}{} = {}{} | {}{}", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt);
break;
case InstrId::cpu_ori:
print_line("{}{} = {}{} | {:#X}", ctx_gpr_prefix(rt), rt, ctx_gpr_prefix(rs), rs, imm);
break;
case InstrId::cpu_nor:
print_line("{}{} = ~({}{} | {}{})", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt);
break;
case InstrId::cpu_xor:
print_line("{}{} = {}{} ^ {}{}", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt);
break;
case InstrId::cpu_xori:
print_line("{}{} = {}{} ^ {:#X}", ctx_gpr_prefix(rt), rt, ctx_gpr_prefix(rs), rs, imm);
break;
case InstrId::cpu_sll:
print_line("{}{} = S32({}{}) << {}", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rt), rt, sa);
break;
case InstrId::cpu_sllv:
print_line("{}{} = S32({}{}) << ({}{} & 31)", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rt), rt, ctx_gpr_prefix(rs), rs);
break;
case InstrId::cpu_sra:
print_line("{}{} = S32(S64({}{}) >> {})", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rt), rt, sa);
break;
case InstrId::cpu_srav:
print_line("{}{} = S32(S64({}{}) >> ({}{} & 31)", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rt), rt, ctx_gpr_prefix(rs), rs);
break;
case InstrId::cpu_srl:
print_line("{}{} = S32(U32({}{}) >> {})", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rt), rt, sa);
break;
case InstrId::cpu_srlv:
print_line("{}{} = S32(U32({}{}) >> ({}{} & 31)", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rt), rt, ctx_gpr_prefix(rs), rs);
break;
case InstrId::cpu_slt:
print_line("{}{} = S64({}{}) < S64({}{}) ? 1 : 0", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt);
break;
case InstrId::cpu_slti:
print_line("{}{} = S64({}{}) < {:#X} ? 1 : 0", ctx_gpr_prefix(rt), rt, ctx_gpr_prefix(rs), rs, (int16_t)imm);
break;
case InstrId::cpu_sltu:
print_line("{}{} = U64({}{}) < U64({}{}) ? 1 : 0", ctx_gpr_prefix(rd), rd, ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt);
break;
case InstrId::cpu_sltiu:
print_line("{}{} = U64({}{}) < {:#X} ? 1 : 0", ctx_gpr_prefix(rt), rt, ctx_gpr_prefix(rs), rs, (int16_t)imm);
break;
case InstrId::cpu_mult:
print_line("uint64_t result = S64({}{}) * S64({}{}); lo = S32(result >> 0); hi = S32(result >> 32)", ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt);
break;
case InstrId::cpu_multu:
print_line("uint64_t result = {}{} * {}{}; lo = S32(result >> 0); hi = S32(result >> 32)", ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt);
break;
case InstrId::cpu_div:
print_line("lo = S32(S64({}{}) / S64({}{})); hi = S32(S64({}{}) % S64({}{}))", ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt, ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt);
break;
case InstrId::cpu_divu:
print_line("lo = S32(U32({}{}) / U32({}{})); hi = S32(U32({}{}) % U32({}{}))", ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt, ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt);
break;
case InstrId::cpu_mflo:
print_line("{}{} = lo", ctx_gpr_prefix(rd), rd);
break;
case InstrId::cpu_mfhi:
print_line("{}{} = hi", ctx_gpr_prefix(rd), rd);
break;
// Loads
// TODO ld
case InstrId::cpu_lw:
print_line("{}{} = MEM_W({:#X}, {}{})", ctx_gpr_prefix(rt), rt, (int16_t)imm, ctx_gpr_prefix(base), base);
break;
case InstrId::cpu_lh:
print_line("{}{} = MEM_H({:#X}, {}{})", ctx_gpr_prefix(rt), rt, (int16_t)imm, ctx_gpr_prefix(base), base);
break;
case InstrId::cpu_lb:
print_line("{}{} = MEM_B({:#X}, {}{})", ctx_gpr_prefix(rt), rt, (int16_t)imm, ctx_gpr_prefix(base), base);
break;
case InstrId::cpu_lhu:
print_line("{}{} = MEM_HU({:#X}, {}{})", ctx_gpr_prefix(rt), rt, (int16_t)imm, ctx_gpr_prefix(base), base);
break;
case InstrId::cpu_lbu:
print_line("{}{} = MEM_BU({:#X}, {}{})", ctx_gpr_prefix(rt), rt, (int16_t)imm, ctx_gpr_prefix(base), base);
break;
// Stores
case InstrId::cpu_sw:
print_line("MEM_W({:#X}, {}{}) = {}{}", (int16_t)imm, ctx_gpr_prefix(base), base, ctx_gpr_prefix(rt), rt);
break;
case InstrId::cpu_sh:
print_line("MEM_H({:#X}, {}{}) = {}{}", (int16_t)imm, ctx_gpr_prefix(base), base, ctx_gpr_prefix(rt), rt);
break;
case InstrId::cpu_sb:
print_line("MEM_B({:#X}, {}{}) = {}{}", (int16_t)imm, ctx_gpr_prefix(base), base, ctx_gpr_prefix(rt), rt);
break;
// TODO lwl, lwr
// examples:
// reg = 11111111 01234567
// mem @ x = 89ABCDEF
// LWL x + 0 -> FFFFFFFF 89ABCDEF
// LWL x + 1 -> FFFFFFFF ABCDEF67
// LWL x + 2 -> FFFFFFFF CDEF4567
// LWL x + 3 -> FFFFFFFF EF234567
// LWR x + 0 -> 00000000 01234589
// LWR x + 1 -> 00000000 012389AB
// LWR x + 2 -> 00000000 0189ABCD
// LWR x + 3 -> FFFFFFFF 89ABCDEF
case InstrId::cpu_lwl:
print_line("{}{} = MEM_WL({:#X}, {}{})", ctx_gpr_prefix(rt), rt, (int16_t)imm, ctx_gpr_prefix(base), base);
break;
case InstrId::cpu_lwr:
print_line("{}{} = MEM_WR({:#X}, {}{})", ctx_gpr_prefix(rt), rt, (int16_t)imm, ctx_gpr_prefix(base), base);
break;
case InstrId::cpu_swl:
print_line("MEM_WL({:#X}, {}{}) = {}{}", (int16_t)imm, ctx_gpr_prefix(base), base, ctx_gpr_prefix(rt), rt);
break;
case InstrId::cpu_swr:
print_line("MEM_WR({:#X}, {}{}) = {}{}", (int16_t)imm, ctx_gpr_prefix(base), base, ctx_gpr_prefix(rt), rt);
break;
// Branches
case InstrId::cpu_jal:
needs_link_branch = true;
print_indent();
// TODO lookup function name
print_branch("{}(rdram, ctx)", "func");
break;
case InstrId::cpu_jalr:
needs_link_branch = true;
print_indent();
// TODO index global function table
print_branch("{}(rdram, ctx)", "func_reg");
break;
case InstrId::cpu_j:
case InstrId::cpu_b:
print_indent();
print_branch("goto L_{:08X}", (uint32_t)instr.getBranchVramGeneric());
break;
case InstrId::cpu_jr:
print_indent();
if (rs == (int)rabbitizer::Registers::Cpu::GprO32::GPR_O32_ra) {
print_branch("return");
} else {
// TODO jump table handling
}
break;
case InstrId::cpu_bnel:
is_branch_likely = true;
[[fallthrough]];
case InstrId::cpu_bne:
print_indent();
print_branch_condition("if (S32({}{}) != S32({}{}))", ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt);
print_branch("goto L_{:08X}", (uint32_t)instr.getBranchVramGeneric());
break;
case InstrId::cpu_beql:
is_branch_likely = true;
[[fallthrough]];
case InstrId::cpu_beq:
print_indent();
print_branch_condition("if (S32({}{}) == S32({}{}))", ctx_gpr_prefix(rs), rs, ctx_gpr_prefix(rt), rt);
print_branch("goto L_{:08X}", (uint32_t)instr.getBranchVramGeneric());
break;
case InstrId::cpu_bnez:
print_indent();
print_branch_condition("if (S32({}{}) != 0)", ctx_gpr_prefix(rs), rs);
print_branch("goto L_{:08X}", (uint32_t)instr.getBranchVramGeneric());
break;
case InstrId::cpu_beqz:
print_indent();
print_branch_condition("if (S32({}{}) == 0)", ctx_gpr_prefix(rs), rs);
print_branch("goto L_{:08X}", (uint32_t)instr.getBranchVramGeneric());
break;
case InstrId::cpu_bgezl:
is_branch_likely = true;
[[fallthrough]];
case InstrId::cpu_bgez:
print_indent();
print_branch_condition("if (S32({}{}) >= 0)", ctx_gpr_prefix(rs), rs);
print_branch("goto L_{:08X}", (uint32_t)instr.getBranchVramGeneric());
break;
case InstrId::cpu_bgtzl:
is_branch_likely = true;
[[fallthrough]];
case InstrId::cpu_bgtz:
print_indent();
print_branch_condition("if (S32({}{}) > 0)", ctx_gpr_prefix(rs), rs);
print_branch("goto L_{:08X}", (uint32_t)instr.getBranchVramGeneric());
break;
case InstrId::cpu_blezl:
is_branch_likely = true;
[[fallthrough]];
case InstrId::cpu_blez:
print_indent();
print_branch_condition("if (S32({}{}) <= 0)", ctx_gpr_prefix(rs), rs);
print_branch("goto L_{:08X}", (uint32_t)instr.getBranchVramGeneric());
break;
case InstrId::cpu_bltzl:
is_branch_likely = true;
[[fallthrough]];
case InstrId::cpu_bltz:
print_indent();
print_branch_condition("if (S32({}{}) < 0)", ctx_gpr_prefix(rs), rs);
print_branch("goto L_{:08X}", (uint32_t)instr.getBranchVramGeneric());
break;
case InstrId::cpu_break:
print_line("do_break();");
break;
// Cop1 loads/stores
case InstrId::cpu_mtc1:
if ((fs & 1) == 0) {
// even fpr
print_line("ctx->f{}.u32l = {}{}", fs, ctx_gpr_prefix(rt), rt);
}
else {
// odd fpr
print_line("ctx->f{}.u32h = {}{}", fs - 1, ctx_gpr_prefix(rt), rt);
}
break;
case InstrId::cpu_mfc1:
if ((fs & 1) == 0) {
// even fpr
print_line("{}{} = ctx->f{}.u32l", ctx_gpr_prefix(rt), rt, fs);
} else {
// odd fpr
print_line("{}{} = ctx->f{}.u32h", ctx_gpr_prefix(rt), rt, fs - 1);
}
break;
case InstrId::cpu_lwc1:
if ((ft & 1) == 0) {
// even fpr
print_line("ctx->f{}.u32l = MEM_W({:#X}, {}{})", ft, (int16_t)imm, ctx_gpr_prefix(base), base);
} else {
// odd fpr
print_line("ctx->f{}.u32h = MEM_W({:#X}, {}{})", ft - 1, (int16_t)imm, ctx_gpr_prefix(base), base);
}
break;
case InstrId::cpu_ldc1:
if ((ft & 1) == 0) {
print_line("ctx->f{}.u64 = MEM_D({:#X}, {}{})", ft, (int16_t)imm, ctx_gpr_prefix(base), base);
} else {
fmt::print(stderr, "Invalid operand for ldc1: f{}\n", ft);
return false;
}
break;
case InstrId::cpu_swc1:
if ((ft & 1) == 0) {
// even fpr
print_line("MEM_W({:#X}, {}{}) = ctx->f{}.u32l", (int16_t)imm, ctx_gpr_prefix(base), base, ft);
} else {
// odd fpr
print_line("MEM_W({:#X}, {}{}) = ctx->f{}.u32h", (int16_t)imm, ctx_gpr_prefix(base), base, ft - 1);
}
break;
case InstrId::cpu_sdc1:
if ((ft & 1) == 0) {
print_line("MEM_D({:#X}, {}{}) = ctx->f{}.u64", (int16_t)imm, ctx_gpr_prefix(base), base, ft);
} else {
fmt::print(stderr, "Invalid operand for sdc1: f{}\n", ft);
return false;
}
break;
// Cop1 compares
case InstrId::cpu_c_lt_s:
if ((fs & 1) == 0 && (ft & 1) == 0) {
print_line("c1cs = ctx->f{}.fl <= ctx->f{}.fl", fs, ft);
} else {
fmt::print(stderr, "Invalid operand for c.lt.s: f{} f{}\n", fs, ft);
return false;
}
break;
case InstrId::cpu_c_lt_d:
if ((fs & 1) == 0 && (ft & 1) == 0) {
print_line("c1cs = ctx->f{}.d <= ctx->f{}.d", fs, ft);
} else {
fmt::print(stderr, "Invalid operand for c.lt.d: f{} f{}\n", fs, ft);
return false;
}
break;
case InstrId::cpu_c_le_s:
if ((fs & 1) == 0 && (ft & 1) == 0) {
print_line("c1cs = ctx->f{}.fl <= ctx->f{}.fl", fs, ft);
} else {
fmt::print(stderr, "Invalid operand for c.le.s: f{} f{}\n", fs, ft);
return false;
}
break;
case InstrId::cpu_c_le_d:
if ((fs & 1) == 0 && (ft & 1) == 0) {
print_line("c1cs = ctx->f{}.d <= ctx->f{}.d", fs, ft);
} else {
fmt::print(stderr, "Invalid operand for c.le.d: f{} f{}\n", fs, ft);
return false;
}
break;
case InstrId::cpu_c_eq_s:
if ((fs & 1) == 0 && (ft & 1) == 0) {
print_line("c1cs = ctx->f{}.fl == ctx->f{}.fl", fs, ft);
} else {
fmt::print(stderr, "Invalid operand for c.eq.s: f{} f{}\n", fs, ft);
return false;
}
break;
case InstrId::cpu_c_eq_d:
if ((fs & 1) == 0 && (ft & 1) == 0) {
print_line("c1cs = ctx->f{}.d == ctx->f{}.d", fs, ft);
} else {
fmt::print(stderr, "Invalid operand for c.eq.d: f{} f{}\n", fs, ft);
return false;
}
break;
// Cop1 branches
case InstrId::cpu_bc1tl:
is_branch_likely = true;
[[fallthrough]];
case InstrId::cpu_bc1t:
print_indent();
print_branch_condition("if (c1cs)", ctx_gpr_prefix(rs), rs);
print_branch("goto L_{:08X}", (uint32_t)instr.getBranchVramGeneric());
break;
case InstrId::cpu_bc1fl:
is_branch_likely = true;
[[fallthrough]];
case InstrId::cpu_bc1f:
print_indent();
print_branch_condition("if (!c1cs)", ctx_gpr_prefix(rs), rs);
print_branch("goto L_{:08X}", (uint32_t)instr.getBranchVramGeneric());
break;
// Cop1 arithmetic
case InstrId::cpu_mov_s:
if ((fd & 1) == 0 && (fs & 1) == 0) {
// even fpr
print_line("ctx->f{}.fl = ctx->f{}.fl", fd, fs);
} else {
fmt::print(stderr, "Invalid operand(s) for mov.s: f{} f{}\n", fd, fs);
return false;
}
break;
case InstrId::cpu_mov_d:
if ((fd & 1) == 0 && (fs & 1) == 0) {
// even fpr
print_line("ctx->f{}.d = ctx->f{}.d", fd, fs);
} else {
fmt::print(stderr, "Invalid operand(s) for mov.d: f{} f{}\n", fd, fs);
return false;
}
break;
case InstrId::cpu_neg_s:
if ((fd & 1) == 0 && (fs & 1) == 0) {
// even fpr
print_line("ctx->f{}.fl = -ctx->f{}.fl", fd, fs);
} else {
fmt::print(stderr, "Invalid operand(s) for neg.s: f{} f{}\n", fd, fs);
return false;
}
break;
case InstrId::cpu_neg_d:
if ((fd & 1) == 0 && (fs & 1) == 0) {
// even fpr
print_line("ctx->f{}.d = -ctx->f{}.d", fd, fs);
} else {
fmt::print(stderr, "Invalid operand(s) for neg.d: f{} f{}\n", fd, fs);
return false;
}
break;
case InstrId::cpu_abs_s:
if ((fd & 1) == 0 && (fs & 1) == 0) {
// even fpr
print_line("ctx->f{}.fl = fabsf(ctx->f{}.fl)", fd, fs);
} else {
fmt::print(stderr, "Invalid operand(s) for abs.s: f{} f{}\n", fd, fs);
return false;
}
break;
case InstrId::cpu_abs_d:
if ((fd & 1) == 0 && (fs & 1) == 0) {
// even fpr
print_line("ctx->f{}.d = fabs(ctx->f{}.d)", fd, fs);
} else {
fmt::print(stderr, "Invalid operand(s) for abs.d: f{} f{}\n", fd, fs);
return false;
}
break;
case InstrId::cpu_sqrt_s:
if ((fd & 1) == 0 && (fs & 1) == 0) {
// even fpr
print_line("ctx->f{}.fl = sqrtf(ctx->f{}.fl)", fd, fs);
} else {
fmt::print(stderr, "Invalid operand(s) for sqrt.s: f{} f{}\n", fd, fs);
return false;
}
break;
case InstrId::cpu_sqrt_d:
if ((fd & 1) == 0 && (fs & 1) == 0) {
// even fpr
print_line("ctx->f{}.d = sqrt(ctx->f{}.d)", fd, fs);
} else {
fmt::print(stderr, "Invalid operand(s) for sqrt.d: f{} f{}\n", fd, fs);
return false;
}
break;
case InstrId::cpu_add_s:
if ((fd & 1) == 0 && (fs & 1) == 0 && (ft & 1) == 0) {
// even fpr
print_line("ctx->f{}.fl = ctx->f{}.fl + ctx->f{}.fl", fd, fs, ft);
} else {
fmt::print(stderr, "Invalid operand(s) for add.s: f{} f{} f{}\n", fd, fs, ft);
return false;
}
break;
case InstrId::cpu_add_d:
if ((fd & 1) == 0 && (fs & 1) == 0 && (ft & 1) == 0) {
// even fpr
print_line("ctx->f{}.d = ctx->f{}.d + ctx->f{}.d", fd, fs, ft);
} else {
fmt::print(stderr, "Invalid operand(s) for add.d: f{} f{} f{}\n", fd, fs, ft);
return false;
}
break;
case InstrId::cpu_sub_s:
if ((fd & 1) == 0 && (fs & 1) == 0 && (ft & 1) == 0) {
// even fpr
print_line("ctx->f{}.fl = ctx->f{}.fl - ctx->f{}.fl", fd, fs, ft);
} else {
fmt::print(stderr, "Invalid operand(s) for sub.s: f{} f{} f{}\n", fd, fs, ft);
return false;
}
break;
case InstrId::cpu_sub_d:
if ((fd & 1) == 0 && (fs & 1) == 0 && (ft & 1) == 0) {
// even fpr
print_line("ctx->f{}.d = ctx->f{}.d - ctx->f{}.d", fd, fs, ft);
} else {
fmt::print(stderr, "Invalid operand(s) for sub.d: f{} f{} f{}\n", fd, fs, ft);
return false;
}
break;
case InstrId::cpu_mul_s:
if ((fd & 1) == 0 && (fs & 1) == 0 && (ft & 1) == 0) {
// even fpr
print_line("ctx->f{}.fl = MUL_S(ctx->f{}.fl, ctx->f{}.fl)", fd, fs, ft);
} else {
fmt::print(stderr, "Invalid operand(s) for mul.s: f{} f{} f{}\n", fd, fs, ft);
return false;
}
break;
case InstrId::cpu_mul_d:
if ((fd & 1) == 0 && (fs & 1) == 0 && (ft & 1) == 0) {
// even fpr
print_line("ctx->f{}.d = MUL_D(ctx->f{}.d, ctx->f{}.d)", fd, fs, ft);
} else {
fmt::print(stderr, "Invalid operand(s) for mul.d: f{} f{} f{}\n", fd, fs, ft);
return false;
}
break;
case InstrId::cpu_div_s:
if ((fd & 1) == 0 && (fs & 1) == 0 && (ft & 1) == 0) {
// even fpr
print_line("ctx->f{}.fl = DIV_S(ctx->f{}.fl, ctx->f{}.fl)", fd, fs, ft);
} else {
fmt::print(stderr, "Invalid operand(s) for div.s: f{} f{} f{}\n", fd, fs, ft);
return false;
}
break;
case InstrId::cpu_div_d:
if ((fd & 1) == 0 && (fs & 1) == 0 && (ft & 1) == 0) {
// even fpr
print_line("ctx->f{}.d = DIV_D(ctx->f{}.d, ctx->f{}.d)", fd, fs, ft);
} else {
fmt::print(stderr, "Invalid operand(s) for div.d: f{} f{} f{}\n", fd, fs, ft);
return false;
}
break;
case InstrId::cpu_cvt_s_w:
if ((fd & 1) == 0 && (fs & 1) == 0) {
// even fpr
print_line("ctx->f{}.fl = CVT_S_W(ctx->f{}.u32l)", fd, fs);
} else {
fmt::print(stderr, "Invalid operand(s) for cvt.s.w: f{} f{}\n", fd, fs);
return false;
}
break;
case InstrId::cpu_cvt_d_w:
if ((fd & 1) == 0 && (fs & 1) == 0) {
// even fpr
print_line("ctx->f{}.d = CVT_D_W(ctx->f{}.u32l)", fd, fs);
} else {
fmt::print(stderr, "Invalid operand(s) for cvt.d.w: f{} f{}\n", fd, fs);
return false;
}
break;
case InstrId::cpu_cvt_d_s:
if ((fd & 1) == 0 && (fs & 1) == 0) {
// even fpr
print_line("ctx->f{}.d = CVT_D_S(ctx->f{}.fl)", fd, fs);
} else {
fmt::print(stderr, "Invalid operand(s) for cvt.d.s: f{} f{}\n", fd, fs);
return false;
}
break;
case InstrId::cpu_cvt_s_d:
if ((fd & 1) == 0 && (fs & 1) == 0) {
// even fpr
print_line("ctx->f{}.fl = CVT_S_D(ctx->f{}.d)", fd, fs);
} else {
fmt::print(stderr, "Invalid operand(s) for cvt.s.d: f{} f{}\n", fd, fs);
return false;
}
break;
case InstrId::cpu_trunc_w_s:
if ((fd & 1) == 0 && (fs & 1) == 0) {
// even fpr
print_line("ctx->f{}.u32l = TRUNC_W_S(ctx->f{}.fl)", fd, fs);
} else {
fmt::print(stderr, "Invalid operand(s) for trunc.w.s: f{} f{}\n", fd, fs);
return false;
}
break;
case InstrId::cpu_trunc_w_d:
if ((fd & 1) == 0 && (fs & 1) == 0) {
// even fpr
print_line("ctx->f{}.u32l = TRUNC_W_D(ctx->f{}.d)", fd, fs);
} else {
fmt::print(stderr, "Invalid operand(s) for trunc.w.d: f{} f{}\n", fd, fs);
return false;
}
break;
default:
fmt::print(stderr, "Unhandled instruction: {}\n", instr.getOpcodeName());
return false;
}
if (emit_link_branch) {
fmt::print(output_file, " after_{}:\n", link_branch_index);
}
return true;
}
bool RecompPort::recompile_function(const RecompPort::Function& func, std::string_view output_path) {
fmt::print("Recompiling {}\n", func.name);
std::vector<rabbitizer::InstructionCpu> instructions;
// Open the output file and write the file header
std::ofstream output_file{ output_path.data() };
fmt::print(output_file,
"#include \"recomp.h\"\n"
"\n"
"void {}(uint8_t* restrict rdram, recomp_context* restrict ctx) {{\n"
// these variables shouldn't need to be preserved across function boundaries, so make them local for more efficient output
" uint64_t hi = 0, lo = 0;\n"
" int c1cs = 0; \n", // cop1 conditional signal
func.name);
// Use a set to sort and deduplicate labels
std::set<uint32_t> branch_labels;
instructions.reserve(func.words.size());
// First pass, disassemble each instruction and collect branch labels
uint32_t vram = func.vram;
for (uint32_t word : func.words) {
const auto& instr = instructions.emplace_back(byteswap(word), vram);
// If this is a branch or a direct jump, add it to the local label list
if (instr.isBranch() || instr.getUniqueId() == rabbitizer::InstrId::UniqueId::cpu_j) {
branch_labels.insert((uint32_t)instr.getBranchVramGeneric());
}
// Advance the vram address by the size of one instruction
vram += 4;
}
// Second pass, emit code for each instruction and emit labels
auto cur_label = branch_labels.cbegin();
vram = func.vram;
int num_link_branches = 0;
int num_likely_branches = 0;
bool needs_link_branch = false;
bool in_likely_delay_slot = false;
for (size_t instr_index = 0; instr_index < instructions.size(); ++instr_index) {
bool had_link_branch = needs_link_branch;
bool is_branch_likely = false;
// If we're in the delay slot of a likely instruction, emit a goto to skip the instruction before any labels
if (in_likely_delay_slot) {
fmt::print(output_file, " goto skip_{};\n", num_likely_branches);
}
// If there are any other branch labels to insert and we're at the next one, insert it
if (cur_label != branch_labels.end() && vram >= *cur_label) {
fmt::print(output_file, "L_{:08X}:\n", *cur_label);
++cur_label;
}
// Process the current instruction and check for errors
if (process_instruction(instr_index, instructions, output_file, false, needs_link_branch, num_link_branches, needs_link_branch, is_branch_likely) == false) {
fmt::print(stderr, "Error in recompilation, clearing {}\n", output_path);
output_file.clear();
return false;
}
// If a link return branch was generated, advance the number of link return branches
if (had_link_branch) {
num_link_branches++;
}
// Now that the instruction has been processed, emit a skip label for the likely branch if needed
if (in_likely_delay_slot) {
fmt::print(output_file, " skip_{}:\n", num_likely_branches);
num_likely_branches++;
}
// Mark the next instruction as being in a likely delay slot if the
in_likely_delay_slot = is_branch_likely;
// Advance the vram address by the size of one instruction
vram += 4;
}
// Terminate the function
fmt::print(output_file, "}}\n");
return true;
}