mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 10:19:47 -04:00
210 lines
6.7 KiB
C++
210 lines
6.7 KiB
C++
#include "code_generator.h"
|
|
#include "config_loader.h"
|
|
#include "control_flow.h"
|
|
|
|
#include <chrono>
|
|
#include <cstdint>
|
|
#include <cstdio>
|
|
#include <exception>
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
namespace fs = std::filesystem;
|
|
|
|
namespace {
|
|
|
|
constexpr uint32_t kBase = 0x80010000u;
|
|
int failures = 0;
|
|
|
|
void check(bool condition, const char* message) {
|
|
if (!condition) {
|
|
std::fprintf(stderr, "FAIL: %s\n", message);
|
|
++failures;
|
|
}
|
|
}
|
|
|
|
void append_word(std::vector<uint8_t>& bytes, uint32_t word) {
|
|
bytes.push_back(static_cast<uint8_t>(word));
|
|
bytes.push_back(static_cast<uint8_t>(word >> 8));
|
|
bytes.push_back(static_cast<uint8_t>(word >> 16));
|
|
bytes.push_back(static_cast<uint8_t>(word >> 24));
|
|
}
|
|
|
|
fs::path write_temp_config(const char* stem, const std::string& body) {
|
|
const auto nonce = std::chrono::high_resolution_clock::now()
|
|
.time_since_epoch().count();
|
|
fs::path path = fs::temp_directory_path() /
|
|
(std::string(stem) + "-" + std::to_string(nonce) + ".toml");
|
|
std::ofstream out(path, std::ios::binary | std::ios::trunc);
|
|
out << body;
|
|
return path;
|
|
}
|
|
|
|
std::string base_config() {
|
|
return R"toml([game]
|
|
name = "Signed Bound Test"
|
|
id = "TEST-00000"
|
|
exe = "TEST.EXE"
|
|
load_address = "0x80010000"
|
|
entry_pc = "0x80010000"
|
|
text_size = "0x1000"
|
|
stack_base = "0x801FFFF0"
|
|
|
|
[recompiler]
|
|
seeds = "seeds.txt"
|
|
out_dir = "generated"
|
|
)toml";
|
|
}
|
|
|
|
bool load_throws_with(const std::string& body, const char* needle) {
|
|
fs::path path = write_temp_config("signed-x-bound", body);
|
|
bool matched = false;
|
|
try {
|
|
(void)PSXRecompV4::load_game_config(path);
|
|
} catch (const std::exception& e) {
|
|
matched = std::string(e.what()).find(needle) != std::string::npos;
|
|
}
|
|
fs::remove(path);
|
|
return matched;
|
|
}
|
|
|
|
PSXRecomp::GeneratedFunction generate_first_instruction(
|
|
uint32_t first_word, const PSXRecomp::CodeGenConfig& config) {
|
|
PSXRecomp::PS1Executable exe{};
|
|
exe.header.load_address = kBase;
|
|
exe.header.initial_pc = kBase;
|
|
exe.header.file_size = 20;
|
|
append_word(exe.code_data, first_word);
|
|
append_word(exe.code_data, 0x00000000u);
|
|
append_word(exe.code_data, 0x24030001u);
|
|
append_word(exe.code_data, 0x03E00008u);
|
|
append_word(exe.code_data, 0x00000000u);
|
|
|
|
PSXRecomp::Function function{};
|
|
function.start_addr = kBase;
|
|
function.end_addr = kBase + 20u;
|
|
function.size = 20u;
|
|
function.name = "signed_bound_test";
|
|
|
|
PSXRecomp::ControlFlowAnalyzer analyzer(exe);
|
|
const auto cfg = analyzer.analyze_function(function);
|
|
PSXRecomp::CodeGenerator generator(exe, config);
|
|
return generator.generate_function(function, cfg);
|
|
}
|
|
|
|
void loader_accepts_lui_and_addiu() {
|
|
fs::path path = write_temp_config("signed-x-bound-valid", base_config() + R"toml(
|
|
[[widescreen.signed_x_bound]]
|
|
address = "0x80010000"
|
|
expected = "0x3C020001"
|
|
|
|
[[widescreen.signed_x_bound]]
|
|
address = "0x80010004"
|
|
expected = "0x2402FF00"
|
|
)toml");
|
|
auto config = PSXRecompV4::load_game_config(path);
|
|
check(config.ws_signed_x_bound_sites.size() == 2,
|
|
"loader accepts LUI plus ADDIU signed_x_bound entries");
|
|
fs::remove(path);
|
|
}
|
|
|
|
void loader_rejects_bad_addiu_shapes() {
|
|
check(load_throws_with(base_config() + R"toml(
|
|
[[widescreen.signed_x_bound]]
|
|
address = "0x80010000"
|
|
expected = "0x30020100"
|
|
)toml", "expected must be LUI or ADDIU"),
|
|
"loader rejects unsupported signed_x_bound opcode");
|
|
|
|
check(load_throws_with(base_config() + R"toml(
|
|
[[widescreen.signed_x_bound]]
|
|
address = "0x80010000"
|
|
expected = "0x20020100"
|
|
)toml", "expected must be LUI or ADDIU"),
|
|
"loader rejects ADDI without a matching codegen/interpreter route");
|
|
|
|
check(load_throws_with(base_config() + R"toml(
|
|
[[widescreen.signed_x_bound]]
|
|
address = "0x80010000"
|
|
expected = "0x2482FF00"
|
|
)toml", "must use rs=$zero"),
|
|
"loader rejects ADDIU signed_x_bound with nonzero rs");
|
|
|
|
check(load_throws_with(base_config() + R"toml(
|
|
[[widescreen.signed_x_bound]]
|
|
address = "0x80010000"
|
|
expected = "0x2400FF00"
|
|
)toml", "must not write $zero"),
|
|
"loader rejects ADDIU signed_x_bound writing zero");
|
|
|
|
check(load_throws_with(base_config() + R"toml(
|
|
[[widescreen.signed_x_bound]]
|
|
address = "0x80010000"
|
|
expected = "0x24020000"
|
|
)toml", "non-zero signed screen edge"),
|
|
"loader rejects ADDIU signed_x_bound with zero immediate");
|
|
}
|
|
|
|
void codegen_routes_bound_kinds() {
|
|
PSXRecomp::CodeGenConfig config{};
|
|
config.emit_comments = true;
|
|
config.ws_signed_x_bound_sites.push_back(
|
|
PSXRecompV4::WidescreenSignedBoundSite{kBase, 0x2402FF00u});
|
|
auto addiu_neg = generate_first_instruction(0x2402FF00u, config).full_code;
|
|
check(addiu_neg.find(
|
|
"cpu->gpr[2] = (uint32_t)psx_ws_screen_x_bound(-256);") !=
|
|
std::string::npos,
|
|
"codegen routes ADDIU negative screen bound to screen helper");
|
|
|
|
config.ws_signed_x_bound_sites[0] =
|
|
PSXRecompV4::WidescreenSignedBoundSite{kBase, 0x24020100u};
|
|
auto addiu_pos = generate_first_instruction(0x24020100u, config).full_code;
|
|
check(addiu_pos.find(
|
|
"cpu->gpr[2] = (uint32_t)psx_ws_screen_x_bound(256);") !=
|
|
std::string::npos,
|
|
"codegen routes ADDIU positive screen bound to screen helper");
|
|
|
|
config.ws_signed_x_bound_sites[0] =
|
|
PSXRecompV4::WidescreenSignedBoundSite{kBase, 0x3402FF00u};
|
|
auto ori = generate_first_instruction(0x3402FF00u, config).full_code;
|
|
check(ori.find("psx_ws_screen_x_bound(65280)") != std::string::npos,
|
|
"ORI bound zero-extends, unlike the ADDIU negative bound");
|
|
|
|
config.ws_signed_x_bound_sites[0] =
|
|
PSXRecompV4::WidescreenSignedBoundSite{kBase, 0x3C020001u};
|
|
auto lui = generate_first_instruction(0x3C020001u, config).full_code;
|
|
check(lui.find(
|
|
"cpu->gpr[2] = (uint32_t)psx_ws_player_x_bound((int32_t)0x00010000);") !=
|
|
std::string::npos,
|
|
"codegen preserves LUI signed-Q16 gameplay helper");
|
|
}
|
|
|
|
void shared_decls_include_screen_helper() {
|
|
PSXRecomp::PS1Executable exe{};
|
|
exe.header.load_address = kBase;
|
|
PSXRecomp::CodeGenerator generator(exe);
|
|
std::vector<PSXRecomp::GeneratedFunction> functions;
|
|
const auto decls = generator.build_shared_decls_header(functions);
|
|
check(decls.find("psx_ws_screen_x_bound") != std::string::npos,
|
|
"shared declarations include psx_ws_screen_x_bound");
|
|
}
|
|
|
|
} // namespace
|
|
|
|
int main() {
|
|
loader_accepts_lui_and_addiu();
|
|
loader_rejects_bad_addiu_shapes();
|
|
codegen_routes_bound_kinds();
|
|
shared_decls_include_screen_helper();
|
|
|
|
if (failures != 0) {
|
|
std::fprintf(stderr, "signed_x_bound_codegen_test: %d failure(s)\n",
|
|
failures);
|
|
return 1;
|
|
}
|
|
std::puts("PASS: signed_x_bound codegen");
|
|
return 0;
|
|
}
|