From 2d1f7ec07f02a58bbf30fb440ec954ca879500e6 Mon Sep 17 00:00:00 2001 From: Ran-j Date: Sat, 27 Dec 2025 11:33:24 -0300 Subject: [PATCH] feat: added missing v0 instructions feat: added threads inplementation on ps2 syscalls feat: patch entrypoint on recompiler feat: ps2 macros now is part of the runtime feat: use thread to hold game loop feat: sanitize functions like point out by chrisking1981 fix: fix old instructions cast fix: fix missing patch for opcode j --- ps2xRecomp/include/ps2recomp/code_generator.h | 14 + ps2xRecomp/include/ps2recomp/elf_parser.h | 1 + ps2xRecomp/include/ps2recomp/ps2_recompiler.h | 2 + ps2xRecomp/src/code_generator.cpp | 144 +++++++++- ps2xRecomp/src/elf_parser.cpp | 9 +- ps2xRecomp/src/ps2_recompiler.cpp | 134 +++++++++- ps2xRuntime/CMakeLists.txt | 4 + ps2xRuntime/include/ps2_runtime.h | 24 +- ps2xRuntime/include/ps2_syscalls.h | 4 + ps2xRuntime/output/ps2_runtime_macros.h | 215 +++++++++++++++ ps2xRuntime/src/lib/ps2_runtime.cpp | 36 ++- ps2xRuntime/src/lib/ps2_syscalls.cpp | 247 ++++++++++++++++-- 12 files changed, 777 insertions(+), 57 deletions(-) create mode 100644 ps2xRuntime/output/ps2_runtime_macros.h diff --git a/ps2xRecomp/include/ps2recomp/code_generator.h b/ps2xRecomp/include/ps2recomp/code_generator.h index 77bcc85..8704772 100644 --- a/ps2xRecomp/include/ps2recomp/code_generator.h +++ b/ps2xRecomp/include/ps2recomp/code_generator.h @@ -10,6 +10,7 @@ namespace ps2recomp { + extern const std::unordered_set kKeywords; class CodeGenerator { @@ -17,6 +18,15 @@ namespace ps2recomp CodeGenerator(const std::vector &symbols); ~CodeGenerator(); + struct BootstrapInfo + { + bool valid = false; + uint32_t entry = 0; + uint32_t bssStart = 0; + uint32_t bssEnd = 0; + uint32_t gp = 0; + }; + std::string generateFunction(const Function &function, const std::vector &instructions, const bool &useHeaders); std::string generateFunctionRegistration(const std::vector &functions, const std::map &stubs); std::string generateMacroHeader(); @@ -24,12 +34,14 @@ namespace ps2recomp const Function &function, const std::unordered_set &internalTargets); void setRenamedFunctions(const std::unordered_map &renames); + void setBootstrapInfo(const BootstrapInfo &info); std::unordered_set collectInternalBranchTargets(const Function &function, const std::vector &instructions); public: std::vector m_symbols; std::unordered_map m_renamedFunctions; + BootstrapInfo m_bootstrapInfo; std::string translateInstruction(const Instruction &inst); std::string translateMMIInstruction(const Instruction &inst); @@ -104,9 +116,11 @@ namespace ps2recomp // Jump Table Generation std::string generateJumpTableSwitch(const Instruction &inst, uint32_t tableAddress, const std::vector &entries); + std::string generateBootstrapFunction() const; Symbol *findSymbolByAddress(uint32_t address); std::string getFunctionName(uint32_t address); + std::string getGeneratedFunctionName(const Function &function); }; } diff --git a/ps2xRecomp/include/ps2recomp/elf_parser.h b/ps2xRecomp/include/ps2recomp/elf_parser.h index 8754dcf..a236511 100644 --- a/ps2xRecomp/include/ps2recomp/elf_parser.h +++ b/ps2xRecomp/include/ps2recomp/elf_parser.h @@ -29,6 +29,7 @@ namespace ps2recomp uint8_t *getSectionData(const std::string §ionName); uint32_t getSectionAddress(const std::string §ionName); uint32_t getSectionSize(const std::string §ionName); + uint32_t getEntryPoint() const; private: std::string m_filePath; diff --git a/ps2xRecomp/include/ps2recomp/ps2_recompiler.h b/ps2xRecomp/include/ps2recomp/ps2_recompiler.h index 0c528ae..ecfc7d4 100644 --- a/ps2xRecomp/include/ps2recomp/ps2_recompiler.h +++ b/ps2xRecomp/include/ps2recomp/ps2_recompiler.h @@ -39,6 +39,8 @@ namespace ps2recomp std::unordered_map> m_decodedFunctions; std::unordered_map m_skipFunctions; std::map m_generatedStubs; + std::unordered_map m_functionRenames; + CodeGenerator::BootstrapInfo m_bootstrapInfo; bool decodeFunction(Function &function); void discoverAdditionalEntryPoints(); diff --git a/ps2xRecomp/src/code_generator.cpp b/ps2xRecomp/src/code_generator.cpp index 4bd4a6c..37a3788 100644 --- a/ps2xRecomp/src/code_generator.cpp +++ b/ps2xRecomp/src/code_generator.cpp @@ -8,6 +8,23 @@ #include #include +namespace ps2recomp +{ + static const std::unordered_set kKeywords = { + "alignas", "alignof", "and", "and_eq", "asm", "auto", "bitand", "bitor", "bool", + "break", "case", "catch", "char", "char8_t", "char16_t", "char32_t", "class", + "compl", "concept", "const", "consteval", "constexpr", "constinit", "const_cast", + "continue", "co_await", "co_return", "co_yield", "decltype", "default", "delete", + "do", "double", "dynamic_cast", "else", "enum", "explicit", "export", "extern", + "false", "float", "for", "friend", "goto", "if", "inline", "int", "long", "mutable", + "namespace", "new", "noexcept", "not", "not_eq", "nullptr", "operator", "or", "or_eq", + "private", "protected", "public", "register", "reinterpret_cast", "requires", "return", + "short", "signed", "sizeof", "static", "static_assert", "static_cast", "struct", + "switch", "template", "this", "thread_local", "throw", "true", "try", "typedef", + "typeid", "typename", "union", "unsigned", "using", "virtual", "void", "volatile", + "wchar_t", "while", "xor", "xor_eq", "std"}; +} + namespace ps2recomp { CodeGenerator::CodeGenerator(const std::vector &symbols) @@ -20,6 +37,11 @@ namespace ps2recomp m_renamedFunctions = renames; } + void CodeGenerator::setBootstrapInfo(const BootstrapInfo &info) + { + m_bootstrapInfo = info; + } + std::string CodeGenerator::getFunctionName(uint32_t address) { auto it = m_renamedFunctions.find(address); @@ -46,13 +68,35 @@ namespace ps2recomp return false; } + static bool isReservedCxxKeyword(const std::string &name) + { + return kKeywords.find(name) != kKeywords.end(); + } + static std::string sanitizeFunctionName(const std::string &name) { + // ugly but will do for now + if (name == "main") + return "ps2_main"; + + if (isReservedCxxKeyword(name)) + return "ps2_" + name; + if (!isReservedCxxIdentifier(name)) return name; return "ps2_" + name; } + std::string CodeGenerator::getGeneratedFunctionName(const Function &function) + { + std::string name = getFunctionName(function.start); + if (name.empty()) + { + name = sanitizeFunctionName(function.name); + } + return name; + } + std::string CodeGenerator::handleBranchDelaySlots(const Instruction &branchInst, const Instruction &delaySlot, const Function &function, const std::unordered_set &internalTargets) { @@ -78,7 +122,11 @@ namespace ps2recomp std::string funcName = getFunctionName(target); if (!funcName.empty()) { - ss << " " << funcName << "(rdram, ctx, runtime); return;\n"; + ss << " " << funcName << "(rdram, ctx, runtime);\n"; + if (branchInst.opcode == OPCODE_J) + { + ss << " return;\n"; + } } else { @@ -268,6 +316,18 @@ namespace ps2recomp ss << "#define PS2_RUNTIME_MACROS_H\n\n"; ss << "#include \n"; ss << "#include // For SSE/AVX intrinsics\n\n"; + ss << "#include \n\n"; + ss << "inline uint32_t ps2_clz32(uint32_t val) {\n"; + ss << "#if defined(_MSC_VER)\n"; + ss << " unsigned long idx;\n"; + ss << " if (_BitScanReverse(&idx, val)) {\n"; + ss << " return 31u - idx;\n"; + ss << " }\n"; + ss << " return 32u;\n"; + ss << "#else\n"; + ss << " return val == 0 ? 32u : (uint32_t)__builtin_clz(val);\n"; + ss << "#endif\n"; + ss << "}\n\n"; ss << "// Basic MIPS arithmetic operations\n"; ss << "#define ADD32(a, b) ((uint32_t)((a) + (b)))\n"; @@ -553,6 +613,36 @@ namespace ps2recomp { std::stringstream ss; + static const std::unordered_set systemCallNames = { + "FlushCache", "ResetEE", "SetMemoryMode", + "CreateThread", "DeleteThread", "StartThread", "ExitThread", "ExitDeleteThread", + "TerminateThread", "SuspendThread", "ResumeThread", "GetThreadId", "ReferThreadStatus", + "SleepThread", "WakeupThread", "iWakeupThread", "ChangeThreadPriority", + "RotateThreadReadyQueue", "ReleaseWaitThread", "iReleaseWaitThread", + "CreateSema", "DeleteSema", "SignalSema", "iSignalSema", "WaitSema", "PollSema", + "iPollSema", "ReferSemaStatus", "iReferSemaStatus", "CreateEventFlag", + "DeleteEventFlag", "SetEventFlag", "iSetEventFlag", "ClearEventFlag", + "iClearEventFlag", "WaitEventFlag", "PollEventFlag", "iPollEventFlag", + "ReferEventFlagStatus", "iReferEventFlagStatus", "SetAlarm", "iSetAlarm", + "CancelAlarm", "iCancelAlarm", "EnableIntc", "DisableIntc", "EnableDmac", + "DisableDmac", "SifStopModule", "SifLoadModule", "SifInitRpc", "SifBindRpc", + "SifCallRpc", "SifRegisterRpc", "SifCheckStatRpc", "SifSetRpcQueue", + "SifRemoveRpcQueue", "SifRemoveRpc", "fioOpen", "fioClose", "fioRead", "fioWrite", + "fioLseek", "fioMkdir", "fioChdir", "fioRmdir", "fioGetstat", "fioRemove", + "GsSetCrt", "GsGetIMR", "GsPutIMR", "GsSetVideoMode", "GetOsdConfigParam", + "SetOsdConfigParam", "GetRomName", "sceSifLoadModule", + "SifSetDChain"}; + + if (systemCallNames.find(function.name) != systemCallNames.end()) + { + std::string sanitizedName = sanitizeFunctionName(function.name); + ss << "// System call wrapper for " << function.name << "\n"; + ss << "void " << sanitizedName << "(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) {\n"; + ss << " ps2_syscalls::" << function.name << "(rdram, ctx, runtime);\n"; + ss << "}\n"; + return ss.str(); + } + if (useHeaders) { ss << "#include \"ps2_runtime_macros.h\"\n"; @@ -565,7 +655,7 @@ namespace ps2recomp ss << "// Function: " << function.name << "\n"; ss << "// Address: 0x" << std::hex << function.start << " - 0x" << function.end << std::dec << "\n"; - std::string sanitizedName = sanitizeFunctionName(function.name); + std::string sanitizedName = getGeneratedFunctionName(function); ss << "void " << sanitizedName << "(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) {\n\n"; for (size_t i = 0; i < instructions.size(); ++i) @@ -1290,7 +1380,7 @@ namespace ps2recomp case MMI_MADDU1: return fmt::format("{{ uint64_t acc = ((uint64_t)ctx->hi1 << 32) | ctx->lo1; uint64_t prod = (uint64_t)GPR_U32(ctx, {}) * (uint64_t)GPR_U32(ctx, {}); uint64_t result = acc + prod; ctx->lo1 = (uint32_t)result; ctx->hi1 = (uint32_t)(result >> 32); }}", rs, rt); case MMI_PLZCW: - return fmt::format("{{ uint32_t val = GPR_U32(ctx, {}); SET_GPR_U32(ctx, {}, val == 0 ? 32 : __builtin_clz(val)); }}", rs, rd); + return fmt::format("{{ uint32_t val = GPR_U32(ctx, {}); SET_GPR_U32(ctx, {}, ps2_clz32(val)); }}", rs, rd); case MMI_PSLLH: return fmt::format("SET_GPR_VEC(ctx, {}, _mm_slli_epi16(GPR_VEC(ctx, {}), {}));", rd, rt, sa); case MMI_PSRLH: @@ -1652,7 +1742,7 @@ namespace ps2recomp case VU0_CR_R: return fmt::format("ctx->vu0_r = _mm_castsi128_ps(GPR_VEC(ctx, {}));", rt); case VU0_CR_I: - return fmt::format("ctx->vu0_i = *(float*)&GPR_U32(ctx, {});", rt); + return fmt::format("{{ uint32_t tmp = GPR_U32(ctx, {}); ctx->vu0_i = *reinterpret_cast(&tmp); }}", rt); case VU0_CR_TPC: return fmt::format("ctx->vu0_tpc = GPR_U32(ctx, {});", rt); case VU0_CR_CMSAR0: @@ -1686,7 +1776,7 @@ namespace ps2recomp case VU0_CR_CLIP2: return fmt::format("ctx->vu0_clip_flags2 = GPR_U32(ctx, {});", rt); case VU0_CR_P: - return fmt::format("ctx->vu0_p = *(float*)&GPR_U32(ctx, {});", rt); + return fmt::format("{{ uint32_t tmp = GPR_U32(ctx, {}); ctx->vu0_p = *reinterpret_cast(&tmp); }}", rt); case VU0_CR_XITOP: return fmt::format("ctx->vu0_xitop = GPR_U32(ctx, {}) & 0x3FF;", rt); case VU0_CR_ITOP: @@ -2558,16 +2648,25 @@ namespace ps2recomp continue; } + std::string generatedName = getGeneratedFunctionName(function); + if (function.isStub) { - stubFunctions.push_back({function.start, sanitizeFunctionName(function.name)}); + stubFunctions.push_back({function.start, generatedName}); } else { - normalFunctions.push_back({function.start, sanitizeFunctionName(function.name)}); + normalFunctions.push_back({function.start, generatedName}); } } + if (m_bootstrapInfo.valid) + { + ss << " // Register ELF entry bootstrap\n"; + ss << " runtime.registerFunction(0x" << std::hex << m_bootstrapInfo.entry << std::dec + << ", entry_" << std::hex << m_bootstrapInfo.entry << std::dec << ");\n\n"; + } + ss << " // Register recompiled functions\n"; for (const auto &function : normalFunctions) { @@ -2648,4 +2747,35 @@ namespace ps2recomp return nullptr; } + + std::string CodeGenerator::generateBootstrapFunction() const + { + if (!m_bootstrapInfo.valid) + return {}; + + std::stringstream ss; + ss << "// Auto-generated bootstrap for ELF entry point\n"; + ss << "void entry_" << std::hex << m_bootstrapInfo.entry << std::dec + << "(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) {\n"; + if (m_bootstrapInfo.bssEnd > m_bootstrapInfo.bssStart) + { + ss << " const uint32_t bss_start = 0x" << std::hex << m_bootstrapInfo.bssStart << ";\n"; + ss << " const uint32_t bss_end = 0x" << std::hex << m_bootstrapInfo.bssEnd << ";\n"; + ss << " __m128i zero = _mm_setzero_si128();\n"; + ss << " for (uint32_t addr = bss_start; addr < bss_end; addr += 16) {\n"; + ss << " WRITE128(addr, zero);\n"; + ss << " }\n\n"; + } + if (m_bootstrapInfo.gp != 0) + { + ss << " SET_GPR_U32(ctx, 28, 0x" << std::hex << m_bootstrapInfo.gp << ");\n"; + } + if (m_bootstrapInfo.bssEnd > m_bootstrapInfo.bssStart) + { + ss << " SET_GPR_U32(ctx, 29, bss_end);\n"; + } + ss << " InitThread(rdram, ctx, runtime);\n"; + ss << "}\n"; + return ss.str(); + } }; diff --git a/ps2xRecomp/src/elf_parser.cpp b/ps2xRecomp/src/elf_parser.cpp index b993a48..9722621 100644 --- a/ps2xRecomp/src/elf_parser.cpp +++ b/ps2xRecomp/src/elf_parser.cpp @@ -131,6 +131,11 @@ namespace ps2recomp return 0; } + uint32_t ElfParser::getEntryPoint() const + { + return static_cast(m_elf->get_entry()); + } + ElfParser::~ElfParser() = default; bool ElfParser::parse() @@ -204,7 +209,7 @@ namespace ps2recomp ELFIO::section *pstrSec = m_elf->sections[psec->get_link()]; ELFIO::string_section_accessor strings(pstrSec); - + for (ELFIO::Elf_Xword j = 0; j < sym_num; ++j) { std::string name; @@ -214,7 +219,7 @@ namespace ps2recomp unsigned char type; ELFIO::Elf_Half section_index; unsigned char other; - + symbols.get_symbol(j, name, value, size, bind, type, section_index, other); // Skip empty symbols or those with invalid section index diff --git a/ps2xRecomp/src/ps2_recompiler.cpp b/ps2xRecomp/src/ps2_recompiler.cpp index 35e4cd5..257f530 100644 --- a/ps2xRecomp/src/ps2_recompiler.cpp +++ b/ps2xRecomp/src/ps2_recompiler.cpp @@ -9,6 +9,7 @@ #include #include #include +#include namespace fs = std::filesystem; @@ -43,6 +44,65 @@ namespace ps2recomp m_sections = m_elfParser->getSections(); m_relocations = m_elfParser->getRelocations(); + if (m_functions.empty()) + { + std::cerr << "No functions found in ELF file." << std::endl; + return false; + } + + { + m_bootstrapInfo = {}; + uint32_t entry = m_elfParser->getEntryPoint(); + std::cout << "ELF entry point: 0x" << std::hex << entry << std::dec << std::endl; + uint32_t bssStart = std::numeric_limits::max(); + uint32_t bssEnd = 0; + for (const auto &sec : m_sections) + { + if (sec.isBSS && sec.size > 0) + { + bssStart = std::min(bssStart, sec.address); + bssEnd = std::max(bssEnd, sec.address + sec.size); + } + } + + uint32_t gp = 0; + for (const auto &sym : m_symbols) + { + if (sym.name == "_gp") + { + gp = sym.address; + break; + } + } + + if (bssStart != std::numeric_limits::max()) + { + std::cout << "BSS range: 0x" << std::hex << bssStart << " - 0x" << bssEnd + << " (size 0x" << (bssEnd - bssStart) << "), gp=0x" << gp << std::dec << std::endl; + } + else + { + std::cout << "No BSS found, gp=0x" << std::hex << gp << std::dec << std::endl; + } + + if (entry != 0) + { + m_bootstrapInfo.valid = true; + m_bootstrapInfo.entry = entry; + if (bssStart != std::numeric_limits::max() && bssEnd > bssStart) + { + m_bootstrapInfo.bssStart = bssStart; + m_bootstrapInfo.bssEnd = bssEnd; + } + else + { + m_bootstrapInfo.bssStart = 0; + m_bootstrapInfo.bssEnd = 0; + } + m_bootstrapInfo.gp = gp; + } + } + std::cout << "Extracted " << m_functions.size() << " functions, " << m_symbols.size() << " symbols, " << m_sections.size() << " sections, " @@ -50,6 +110,7 @@ namespace ps2recomp m_decoder = std::make_unique(); m_codeGenerator = std::make_unique(m_symbols); + m_codeGenerator->setBootstrapInfo(m_bootstrapInfo); fs::create_directories(m_config.outputPath); @@ -116,20 +177,43 @@ namespace ps2recomp { try { - std::unordered_map renamed; + m_functionRenames.clear(); + + std::unordered_map nameCounts; for (const auto &function : m_functions) { if (!function.isRecompiled) continue; std::string sanitized = sanitizeFunctionName(function.name); - if (sanitized != function.name) + nameCounts[sanitized]++; + } + + for (const auto &function : m_functions) + { + if (!function.isRecompiled) + continue; + + std::string sanitized = sanitizeFunctionName(function.name); + bool isDuplicate = nameCounts[sanitized] > 1; + + if (isDuplicate || sanitized != function.name) { - renamed[function.start] = sanitized; + std::stringstream ss; + if (isDuplicate) + { + ss << sanitized << "_0x" << std::hex << function.start; + } + else + { + ss << sanitized; + } + m_functionRenames[function.start] = ss.str(); } } + if (m_codeGenerator) { - m_codeGenerator->setRenamedFunctions(renamed); + m_codeGenerator->setRenamedFunctions(m_functionRenames); } generateFunctionHeader(); @@ -142,6 +226,12 @@ namespace ps2recomp combinedOutput << "#include \"ps2_runtime_macros.h\"\n"; combinedOutput << "#include \"ps2_runtime.h\"\n"; combinedOutput << "#include \"ps2_recompiled_stubs.h\"\n"; + combinedOutput << "#include \"ps2_syscalls.h\"\n"; + if (m_bootstrapInfo.valid) + { + combinedOutput << "\n" + << m_codeGenerator->generateBootstrapFunction() << "\n\n"; + } for (const auto &function : m_functions) { @@ -179,6 +269,17 @@ namespace ps2recomp } else { + if (m_bootstrapInfo.valid) + { + std::stringstream boot; + boot << "#include \"ps2_recompiled_functions.h\"\n\n"; + boot << "#include \"ps2_runtime_macros.h\"\n"; + boot << "#include \"ps2_runtime.h\"\n\n"; + boot << m_codeGenerator->generateBootstrapFunction() << "\n"; + fs::path bootPath = fs::path(m_config.outputPath) / "ps2_entry_bootstrap.cpp"; + writeToFile(bootPath.string(), boot.str()); + } + for (const auto &function : m_functions) { if (!function.isRecompiled || function.isStub) @@ -282,10 +383,23 @@ namespace ps2recomp { if (function.isRecompiled) { - ss << "void " << sanitizeFunctionName(function.name) << "(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime);\n"; + std::string finalName = sanitizeFunctionName(function.name); + auto renameIt = m_functionRenames.find(function.start); + if (renameIt != m_functionRenames.end()) + { + finalName = renameIt->second; + } + + ss << "void " << finalName << "(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime);\n"; } } + if (m_bootstrapInfo.valid) + { + ss << "void entry_" << std::hex << m_bootstrapInfo.entry << std::dec + << "(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime);\n"; + } + ss << "\n#endif // PS2_RECOMPILED_FUNCTIONS_H\n"; fs::path headerPath = fs::path(m_config.outputPath) / "ps2_recompiled_functions.h"; @@ -524,6 +638,16 @@ namespace ps2recomp std::string PS2Recompiler::sanitizeFunctionName(const std::string &name) const { + if (name == "main") + { + return "ps2_main"; + } + + if (ps2recomp::kKeywords.find(name) != ps2recomp::kKeywords.end()) + { + return "ps2_" + name; + } + if (name.size() >= 2 && name[0] == '_' && (name[1] == '_' || std::isupper(static_cast(name[1])))) { return "ps2_" + name; diff --git a/ps2xRuntime/CMakeLists.txt b/ps2xRuntime/CMakeLists.txt index 75a7284..4243de8 100644 --- a/ps2xRuntime/CMakeLists.txt +++ b/ps2xRuntime/CMakeLists.txt @@ -33,6 +33,10 @@ add_executable(ps2EntryRunner ${RUNNER_SRC_FILES} ) +if (MSVC) + target_compile_options(ps2EntryRunner PRIVATE /FS /Z7) +endif() + target_include_directories(ps2_runtime PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include ) diff --git a/ps2xRuntime/include/ps2_runtime.h b/ps2xRuntime/include/ps2_runtime.h index a1dcf54..bfdab6c 100644 --- a/ps2xRuntime/include/ps2_runtime.h +++ b/ps2xRuntime/include/ps2_runtime.h @@ -96,6 +96,8 @@ struct alignas(16) R5900Context uint32_t vu0_fbrst4; // FBRST4 uint32_t vu0_itop; uint32_t vu0_info; + uint32_t vu0_xitop; // VU0 XITOP - input ITOP for VIF/VU sync + uint32_t vu0_pc; float vu0_cf[4]; // VU0 FMAC control floating-point registers @@ -168,6 +170,18 @@ struct alignas(16) R5900Context vu0_fbrst2 = 0; vu0_fbrst3 = 0; vu0_fbrst4 = 0; + vu0_xitop = 0; + vu0_pc = 0; + vu0_tpc = 0; + vu0_vpu_stat2 = 0; + vu0_tpc2 = 0; + vu0_cmsar1 = 0; + vu0_vpu_stat3 = 0; + vu0_cmsar2 = 0; + vu0_vpu_stat4 = 0; + vu0_itop = 0; + vu0_info = 0; + // Reset COP0 registers cop0_index = 0; @@ -193,16 +207,6 @@ struct alignas(16) R5900Context cop0_taghi = 0; cop0_errorepc = 0; - vu0_tpc = 0; - vu0_vpu_stat2 = 0; - vu0_tpc2 = 0; - vu0_cmsar1 = 0; - vu0_vpu_stat3 = 0; - vu0_cmsar2 = 0; - vu0_vpu_stat4 = 0; - vu0_itop = 0; - vu0_info = 0; - // Reset COP1 state fcr31 = 0; } diff --git a/ps2xRuntime/include/ps2_syscalls.h b/ps2xRuntime/include/ps2_syscalls.h index 6a0b52c..088b301 100644 --- a/ps2xRuntime/include/ps2_syscalls.h +++ b/ps2xRuntime/include/ps2_syscalls.h @@ -3,6 +3,10 @@ #include "ps2_runtime.h" #include +#include + +// Number of active host threads spawned for PS2 thread emulation +extern std::atomic g_activeThreads; static std::mutex g_sys_fd_mutex; diff --git a/ps2xRuntime/output/ps2_runtime_macros.h b/ps2xRuntime/output/ps2_runtime_macros.h new file mode 100644 index 0000000..bf02183 --- /dev/null +++ b/ps2xRuntime/output/ps2_runtime_macros.h @@ -0,0 +1,215 @@ +#ifndef PS2_RUNTIME_MACROS_H +#define PS2_RUNTIME_MACROS_H + +#include +#include // For SSE/AVX intrinsics + +#include + +inline uint32_t ps2_clz32(uint32_t val) { +#if defined(_MSC_VER) + unsigned long idx; + if (_BitScanReverse(&idx, val)) { + return 31u - idx; + } + return 32u; +#else + return val == 0 ? 32u : (uint32_t)__builtin_clz(val); +#endif +} + +// Basic MIPS arithmetic operations +#define ADD32(a, b) ((uint32_t)((a) + (b))) +#define ADD32_OV(rs, rt, result32, overflow) do { int32_t _a = (int32_t)(rs); int32_t _b = (int32_t)(rt); int32_t _r = _a + _b; overflow = (((_a ^ _b) >= 0) && ((_a ^ _r) < 0)); result32 = (uint32_t)_r; } while (0); +#define SUB32(a, b) ((uint32_t)((a) - (b))) +#define SUB32_OV(rs, rt, result32, overflow) do { int32_t _a = (int32_t)(rs); int32_t _b = (int32_t)(rt); int32_t _r = _a - _b; overflow = (((_a ^ _b) < 0) && ((_a ^ _r) < 0)); result32 = (uint32_t)_r; } while (0); +#define MUL32(a, b) ((uint32_t)((a) * (b))) +#define DIV32(a, b) ((uint32_t)((a) / (b))) +#define AND32(a, b) ((uint32_t)((a) & (b))) +#define OR32(a, b) ((uint32_t)((a) | (b))) +#define XOR32(a, b) ((uint32_t)((a) ^ (b))) +#define NOR32(a, b) ((uint32_t)(~((a) | (b)))) +#define SLL32(a, b) ((uint32_t)((a) << (b))) +#define SRL32(a, b) ((uint32_t)((a) >> (b))) +#define SRA32(a, b) ((uint32_t)((int32_t)(a) >> (b))) +#define SLT32(a, b) ((uint32_t)((int32_t)(a) < (int32_t)(b) ? 1 : 0)) +#define SLTU32(a, b) ((uint32_t)((a) < (b) ? 1 : 0)) + +// PS2-specific 128-bit MMI operations +#define PS2_PEXTLW(a, b) _mm_unpacklo_epi32((__m128i)(b), (__m128i)(a)) +#define PS2_PEXTUW(a, b) _mm_unpackhi_epi32((__m128i)(b), (__m128i)(a)) +#define PS2_PEXTLH(a, b) _mm_unpacklo_epi16((__m128i)(b), (__m128i)(a)) +#define PS2_PEXTUH(a, b) _mm_unpackhi_epi16((__m128i)(b), (__m128i)(a)) +#define PS2_PEXTLB(a, b) _mm_unpacklo_epi8((__m128i)(b), (__m128i)(a)) +#define PS2_PEXTUB(a, b) _mm_unpackhi_epi8((__m128i)(b), (__m128i)(a)) +#define PS2_PADDW(a, b) _mm_add_epi32((__m128i)(a), (__m128i)(b)) +#define PS2_PSUBW(a, b) _mm_sub_epi32((__m128i)(a), (__m128i)(b)) +#define PS2_PMAXW(a, b) _mm_max_epi32((__m128i)(a), (__m128i)(b)) +#define PS2_PMINW(a, b) _mm_min_epi32((__m128i)(a), (__m128i)(b)) +#define PS2_PADDH(a, b) _mm_add_epi16((__m128i)(a), (__m128i)(b)) +#define PS2_PSUBH(a, b) _mm_sub_epi16((__m128i)(a), (__m128i)(b)) +#define PS2_PMAXH(a, b) _mm_max_epi16((__m128i)(a), (__m128i)(b)) +#define PS2_PMINH(a, b) _mm_min_epi16((__m128i)(a), (__m128i)(b)) +#define PS2_PADDB(a, b) _mm_add_epi8((__m128i)(a), (__m128i)(b)) +#define PS2_PSUBB(a, b) _mm_sub_epi8((__m128i)(a), (__m128i)(b)) +#define PS2_PAND(a, b) _mm_and_si128((__m128i)(a), (__m128i)(b)) +#define PS2_POR(a, b) _mm_or_si128((__m128i)(a), (__m128i)(b)) +#define PS2_PXOR(a, b) _mm_xor_si128((__m128i)(a), (__m128i)(b)) +#define PS2_PNOR(a, b) _mm_xor_si128(_mm_or_si128((__m128i)(a), (__m128i)(b)), _mm_set1_epi32(0xFFFFFFFF)) + +// PS2 VU (Vector Unit) operations +#define PS2_VADD(a, b) _mm_add_ps((__m128)(a), (__m128)(b)) +#define PS2_VSUB(a, b) _mm_sub_ps((__m128)(a), (__m128)(b)) +#define PS2_VMUL(a, b) _mm_mul_ps((__m128)(a), (__m128)(b)) +#define PS2_VDIV(a, b) _mm_div_ps((__m128)(a), (__m128)(b)) +#define PS2_VMULQ(a, q) _mm_mul_ps((__m128)(a), _mm_set1_ps(q)) + +// Memory access helpers +#define READ8(addr) (*(uint8_t*)((rdram) + ((addr) & PS2_RAM_MASK))) +#define READ16(addr) (*(uint16_t*)((rdram) + ((addr) & PS2_RAM_MASK))) +#define READ32(addr) (*(uint32_t*)((rdram) + ((addr) & PS2_RAM_MASK))) +#define READ64(addr) (*(uint64_t*)((rdram) + ((addr) & PS2_RAM_MASK))) +#define READ128(addr) (*((__m128i*)((rdram) + ((addr) & PS2_RAM_MASK)))) +#define WRITE8(addr, val) (*(uint8_t*)((rdram) + ((addr) & PS2_RAM_MASK)) = (val)) +#define WRITE16(addr, val) (*(uint16_t*)((rdram) + ((addr) & PS2_RAM_MASK)) = (val)) +#define WRITE32(addr, val) (*(uint32_t*)((rdram) + ((addr) & PS2_RAM_MASK)) = (val)) +#define WRITE64(addr, val) (*(uint64_t*)((rdram) + ((addr) & PS2_RAM_MASK)) = (val)) +#define WRITE128(addr, val) (*((__m128i*)((rdram) + ((addr) & PS2_RAM_MASK))) = (val)) + +#define PS2_PCGTW(a, b) _mm_cmpgt_epi32((__m128i)(a), (__m128i)(b)) +#define PS2_PCGTH(a, b) _mm_cmpgt_epi16((__m128i)(a), (__m128i)(b)) +#define PS2_PCGTB(a, b) _mm_cmpgt_epi8((__m128i)(a), (__m128i)(b)) +#define PS2_PCEQW(a, b) _mm_cmpeq_epi32((__m128i)(a), (__m128i)(b)) +#define PS2_PCEQH(a, b) _mm_cmpeq_epi16((__m128i)(a), (__m128i)(b)) +#define PS2_PCEQB(a, b) _mm_cmpeq_epi8((__m128i)(a), (__m128i)(b)) +#define PS2_PABSW(a) _mm_abs_epi32((__m128i)(a)) +#define PS2_PABSH(a) _mm_abs_epi16((__m128i)(a)) +#define PS2_PABSB(a) _mm_abs_epi8((__m128i)(a)) +#define PS2_PPACW(a, b) _mm_packs_epi32((__m128i)(b), (__m128i)(a)) +#define PS2_PPACH(a, b) _mm_packs_epi16((__m128i)(b), (__m128i)(a)) +#define PS2_PPACB(a, b) _mm_packus_epi16(_mm_packs_epi32((__m128i)(b), (__m128i)(a)), _mm_setzero_si128()) +#define PS2_PINTH(a, b) _mm_unpacklo_epi16(_mm_shuffle_epi32((__m128i)(b), _MM_SHUFFLE(3,2,1,0)), _mm_shuffle_epi32((__m128i)(a), _MM_SHUFFLE(3,2,1,0))) +#define PS2_PINTEH(a, b) _mm_unpackhi_epi16(_mm_shuffle_epi32((__m128i)(b), _MM_SHUFFLE(3,2,1,0)), _mm_shuffle_epi32((__m128i)(a), _MM_SHUFFLE(3,2,1,0))) +#define PS2_PMADDW(a, b) _mm_add_epi32(_mm_mullo_epi32(_mm_shuffle_epi32((__m128i)(a), _MM_SHUFFLE(1,0,3,2)), _mm_shuffle_epi32((__m128i)(b), _MM_SHUFFLE(1,0,3,2))), _mm_mullo_epi32(_mm_shuffle_epi32((__m128i)(a), _MM_SHUFFLE(3,2,1,0)), _mm_shuffle_epi32((__m128i)(b), _MM_SHUFFLE(3,2,1,0)))) +#define PS2_PSLLVW(a, b) _mm_custom_sllv_epi32((__m128i)(a), (__m128i)(b)) +#define PS2_PSRLVW(a, b) _mm_custom_srlv_epi32((__m128i)(a), (__m128i)(b)) +#define PS2_PSRAVW(a, b) _mm_custom_srav_epi32((__m128i)(a), (__m128i)(b)) +inline __m128i _mm_custom_sllv_epi32(__m128i a, __m128i count) { + int32_t a_arr[4], count_arr[4], result[4]; + _mm_storeu_si128((__m128i*)a_arr, a); + _mm_storeu_si128((__m128i*)count_arr, count); + for (int i = 0; i < 4; i++) { + result[i] = a_arr[i] << (count_arr[i] & 0x1F); + } + return _mm_loadu_si128((__m128i*)result); +} + +inline __m128i _mm_custom_srlv_epi32(__m128i a, __m128i count) { + int32_t a_arr[4], count_arr[4], result[4]; + _mm_storeu_si128((__m128i*)a_arr, a); + _mm_storeu_si128((__m128i*)count_arr, count); + for (int i = 0; i < 4; i++) { + result[i] = (uint32_t)a_arr[i] >> (count_arr[i] & 0x1F); + } + return _mm_loadu_si128((__m128i*)result); +} + +inline __m128i _mm_custom_srav_epi32(__m128i a, __m128i count) { + int32_t a_arr[4], count_arr[4], result[4]; + _mm_storeu_si128((__m128i*)a_arr, a); + _mm_storeu_si128((__m128i*)count_arr, count); + for (int i = 0; i < 4; i++) { + result[i] = a_arr[i] >> (count_arr[i] & 0x1F); + } + return _mm_loadu_si128((__m128i*)result); +} + +#define PS2_PMFHL_LW(hi, lo) _mm_unpacklo_epi64(lo, hi) +#define PS2_PMFHL_UW(hi, lo) _mm_unpackhi_epi64(lo, hi) +#define PS2_PMFHL_SLW(hi, lo) _mm_packs_epi32(lo, hi) +#define PS2_PMFHL_LH(hi, lo) _mm_shuffle_epi32(_mm_packs_epi32(lo, hi), _MM_SHUFFLE(3,1,2,0)) +#define PS2_PMFHL_SH(hi, lo) _mm_shufflehi_epi16(_mm_shufflelo_epi16(_mm_packs_epi32(lo, hi), _MM_SHUFFLE(3,1,2,0)), _MM_SHUFFLE(3,1,2,0)) +// FPU (COP1) operations +#define FPU_ADD_S(a, b) ((float)(a) + (float)(b)) +#define FPU_SUB_S(a, b) ((float)(a) - (float)(b)) +#define FPU_MUL_S(a, b) ((float)(a) * (float)(b)) +#define FPU_DIV_S(a, b) ((float)(a) / (float)(b)) +#define FPU_SQRT_S(a) sqrtf((float)(a)) +#define FPU_ABS_S(a) fabsf((float)(a)) +#define FPU_MOV_S(a) ((float)(a)) +#define FPU_NEG_S(a) (-(float)(a)) +#define FPU_ROUND_L_S(a) ((int64_t)roundf((float)(a))) +#define FPU_TRUNC_L_S(a) ((int64_t)(float)(a)) +#define FPU_CEIL_L_S(a) ((int64_t)ceilf((float)(a))) +#define FPU_FLOOR_L_S(a) ((int64_t)floorf((float)(a))) +#define FPU_ROUND_W_S(a) ((int32_t)roundf((float)(a))) +#define FPU_TRUNC_W_S(a) ((int32_t)(float)(a)) +#define FPU_CEIL_W_S(a) ((int32_t)ceilf((float)(a))) +#define FPU_FLOOR_W_S(a) ((int32_t)floorf((float)(a))) +#define FPU_CVT_S_W(a) ((float)(int32_t)(a)) +#define FPU_CVT_S_L(a) ((float)(int64_t)(a)) +#define FPU_CVT_W_S(a) ((int32_t)(float)(a)) +#define FPU_CVT_L_S(a) ((int64_t)(float)(a)) +#define FPU_C_F_S(a, b) (0) +#define FPU_C_UN_S(a, b) (isnan((float)(a)) || isnan((float)(b))) +#define FPU_C_EQ_S(a, b) ((float)(a) == (float)(b)) +#define FPU_C_UEQ_S(a, b) ((float)(a) == (float)(b) || isnan((float)(a)) || isnan((float)(b))) +#define FPU_C_OLT_S(a, b) ((float)(a) < (float)(b)) +#define FPU_C_ULT_S(a, b) ((float)(a) < (float)(b) || isnan((float)(a)) || isnan((float)(b))) +#define FPU_C_OLE_S(a, b) ((float)(a) <= (float)(b)) +#define FPU_C_ULE_S(a, b) ((float)(a) <= (float)(b) || isnan((float)(a)) || isnan((float)(b))) +#define FPU_C_SF_S(a, b) (0) +#define FPU_C_NGLE_S(a, b) (isnan((float)(a)) || isnan((float)(b))) +#define FPU_C_SEQ_S(a, b) ((float)(a) == (float)(b)) +#define FPU_C_NGL_S(a, b) ((float)(a) == (float)(b) || isnan((float)(a)) || isnan((float)(b))) +#define FPU_C_LT_S(a, b) ((float)(a) < (float)(b)) +#define FPU_C_NGE_S(a, b) ((float)(a) < (float)(b) || isnan((float)(a)) || isnan((float)(b))) +#define FPU_C_LE_S(a, b) ((float)(a) <= (float)(b)) +#define FPU_C_NGT_S(a, b) ((float)(a) <= (float)(b) || isnan((float)(a)) || isnan((float)(b))) + +#define PS2_QFSRV(rs, rt, sa) _mm_or_si128(_mm_srl_epi32(rt, _mm_cvtsi32_si128(sa)), _mm_sll_epi32(rs, _mm_cvtsi32_si128(32 - sa))) +#define PS2_PCPYLD(rs, rt) _mm_unpacklo_epi64(rt, rs) +#define PS2_PEXEH(rs) _mm_shufflelo_epi16(_mm_shufflehi_epi16(rs, _MM_SHUFFLE(2, 3, 0, 1)), _MM_SHUFFLE(2, 3, 0, 1)) +#define PS2_PEXEW(rs) _mm_shuffle_epi32(rs, _MM_SHUFFLE(2, 3, 0, 1)) +#define PS2_PROT3W(rs) _mm_shuffle_epi32(rs, _MM_SHUFFLE(0, 3, 2, 1)) +// Additional VU0 operations +#define PS2_VSQRT(x) sqrtf(x) +#define PS2_VRSQRT(x) (1.0f / sqrtf(x)) +#define PS2_VCALLMS(addr) // VU0 microprogram calls not supported directly +#define PS2_VCALLMSR(reg) // VU0 microprogram calls not supported directly +#define GPR_U32(ctx_ptr, reg_idx) ((reg_idx == 0) ? 0U : ctx_ptr->r[reg_idx].m128i_u32[0]) +#define GPR_S32(ctx_ptr, reg_idx) ((reg_idx == 0) ? 0 : ctx_ptr->r[reg_idx].m128i_i32[0]) +#define GPR_U64(ctx_ptr, reg_idx) ((reg_idx == 0) ? 0ULL : ctx_ptr->r[reg_idx].m128i_u64[0]) +#define GPR_S64(ctx_ptr, reg_idx) ((reg_idx == 0) ? 0LL : ctx_ptr->r[reg_idx].m128i_i64[0]) +#define GPR_VEC(ctx_ptr, reg_idx) ((reg_idx == 0) ? _mm_setzero_si128() : ctx_ptr->r[reg_idx]) +#define SET_GPR_U32(ctx_ptr, reg_idx, val) \ + do \ + { \ + if (reg_idx != 0) \ + ctx_ptr->r[reg_idx] = _mm_set_epi32(0, 0, 0, (val)); \ + } while (0) +#define SET_GPR_S32(ctx_ptr, reg_idx, val) \ + do \ + { \ + if (reg_idx != 0) \ + ctx_ptr->r[reg_idx] = _mm_set_epi32(0, 0, 0, (val)); \ + } while (0) +#define SET_GPR_U64(ctx_ptr, reg_idx, val) \ + do \ + { \ + if (reg_idx != 0) \ + ctx_ptr->r[reg_idx] = _mm_set_epi64x(0, (val)); \ + } while (0) +#define SET_GPR_S64(ctx_ptr, reg_idx, val) \ + do \ + { \ + if (reg_idx != 0) \ + ctx_ptr->r[reg_idx] = _mm_set_epi64x(0, (val)); \ + } while (0) +#define SET_GPR_VEC(ctx_ptr, reg_idx, val) \ + do \ + { \ + if (reg_idx != 0) \ + ctx_ptr->r[reg_idx] = (val); \ + } while (0) +#endif // PS2_RUNTIME_MACROS_H diff --git a/ps2xRuntime/src/lib/ps2_runtime.cpp b/ps2xRuntime/src/lib/ps2_runtime.cpp index 59acf05..9b63a4c 100644 --- a/ps2xRuntime/src/lib/ps2_runtime.cpp +++ b/ps2xRuntime/src/lib/ps2_runtime.cpp @@ -1,4 +1,5 @@ #include "ps2_runtime.h" +#include "ps2_syscalls.h" #include #include #include @@ -49,7 +50,7 @@ struct ProgramHeader }; #define PT_LOAD 1 // Loadable segment - + static constexpr int FB_WIDTH = 640; static constexpr int FB_HEIGHT = 448; static constexpr uint32_t DEFAULT_FB_ADDR = 0x00100000; // location in RDRAM the guest will draw to @@ -175,6 +176,12 @@ bool PS2Runtime::loadELF(const std::string &elfPath) m_loadedModules.push_back(module); + // Debug: peek at some early globals to verify init state + uint32_t dbg_addr = 0x00300000 + 11240; + uint8_t *dbg_base = m_memory.getRDRAM(); + uint32_t dbg_val = *reinterpret_cast(dbg_base + (dbg_addr & PS2_RAM_MASK)); + std::cout << "Debug: [0x" << std::hex << dbg_addr << "] = 0x" << dbg_val << std::dec << std::endl; + std::cout << "ELF file loaded successfully. Entry point: 0x" << std::hex << m_cpuContext.pc << std::dec << std::endl; return true; } @@ -298,31 +305,44 @@ void PS2Runtime::run() Texture2D frameTex = LoadTextureFromImage(blank); UnloadImage(blank); - std::atomic running{true}; + g_activeThreads.store(1, std::memory_order_relaxed); - std::thread gameThread([&, entryPoint]() { + std::thread gameThread([&, entryPoint]() + { try { entryPoint(m_memory.getRDRAM(), &m_cpuContext, this); + std::cout << "Game thread returned. PC=0x" << std::hex << m_cpuContext.pc + << " RA=0x" << m_cpuContext.r[31].m128i_u32[0] << std::dec << std::endl; } catch (const std::exception &e) { std::cerr << "Error during program execution: " << e.what() << std::endl; } - running = false; - }); + g_activeThreads.fetch_sub(1, std::memory_order_relaxed); }); - while (running && !WindowShouldClose()) + uint64_t tick = 0; + while (g_activeThreads.load(std::memory_order_relaxed) > 0) { + if ((tick++ % 120) == 0) + { + std::cout << "[run] activeThreads=" << g_activeThreads.load(std::memory_order_relaxed) << std::endl; + } UploadFrame(frameTex, this); BeginDrawing(); ClearBackground(BLACK); DrawTexture(frameTex, 0, 0, WHITE); EndDrawing(); + + if (WindowShouldClose()) + { + std::cout << "[run] window close requested, breaking out of loop" << std::endl; + break; + } } - if (!running) + if (g_activeThreads.load(std::memory_order_relaxed) == 0) { // Game thread finished on its own if (gameThread.joinable()) @@ -341,4 +361,6 @@ void PS2Runtime::run() UnloadTexture(frameTex); CloseWindow(); + + std::cout << "[run] exiting loop, activeThreads=" << g_activeThreads.load(std::memory_order_relaxed) << std::endl; } diff --git a/ps2xRuntime/src/lib/ps2_syscalls.cpp b/ps2xRuntime/src/lib/ps2_syscalls.cpp index 7a75b81..920a631 100644 --- a/ps2xRuntime/src/lib/ps2_syscalls.cpp +++ b/ps2xRuntime/src/lib/ps2_syscalls.cpp @@ -1,6 +1,7 @@ #include "ps2_syscalls.h" #include "ps2_runtime.h" +#include "ps2_runtime_macros.h" #include "ps2_stubs.h" #include #include @@ -9,11 +10,43 @@ #include #include #include +#include +#include +#include #include std::unordered_map g_fileDescriptors; int g_nextFd = 3; // Start after stdin, stdout, stderr +struct ThreadInfo +{ + uint32_t entry = 0; + uint32_t stack = 0; + uint32_t stackSize = 0; + uint32_t gp = 0; + uint32_t priority = 0; + uint32_t attr = 0; + uint32_t option = 0; + uint32_t arg = 0; + bool started = false; +}; + +struct SemaInfo +{ + int count = 0; + int maxCount = 0; + std::mutex m; + std::condition_variable cv; +}; + +static std::unordered_map g_threads; +static int g_nextThreadId = 2; // Reserve 1 for the main thread +static thread_local int g_currentThreadId = 1; + +static std::unordered_map> g_semas; +static int g_nextSemaId = 1; +std::atomic g_activeThreads{0}; + int allocatePs2Fd(FILE *file) { if (!file) @@ -118,133 +151,295 @@ namespace ps2_syscalls void CreateThread(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) { - // TODO + uint32_t paramAddr = getRegU32(ctx, 4); // $a0 points to ThreadParam + const uint32_t *param = reinterpret_cast(getConstMemPtr(rdram, paramAddr)); + + if (!param) + { + std::cerr << "CreateThread error: invalid ThreadParam address 0x" << std::hex << paramAddr << std::dec << std::endl; + setReturnS32(ctx, -1); + return; + } + + ThreadInfo info{}; + info.attr = param[0]; + info.entry = param[1]; + info.stack = param[2]; + info.stackSize = param[3]; + info.gp = param[5]; // Often gp is at offset 20 + info.priority = param[4]; // Commonly priority/init attr slot + info.option = param[6]; + + int id = g_nextThreadId++; + g_threads[id] = info; + + std::cout << "[CreateThread] id=" << id + << " entry=0x" << std::hex << info.entry + << " stack=0x" << info.stack + << " size=0x" << info.stackSize + << " gp=0x" << info.gp + << " prio=" << std::dec << info.priority << std::endl; + + setReturnS32(ctx, id); } void DeleteThread(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) { - // TODO + int tid = static_cast(getRegU32(ctx, 4)); // $a0 + g_threads.erase(tid); + setReturnS32(ctx, 0); } void StartThread(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) { - // TODO + int tid = static_cast(getRegU32(ctx, 4)); // $a0 = thread id + uint32_t arg = getRegU32(ctx, 5); // $a1 = user arg + + auto it = g_threads.find(tid); + if (it == g_threads.end()) + { + std::cerr << "StartThread error: unknown thread id " << tid << std::endl; + setReturnS32(ctx, -1); + return; + } + + ThreadInfo &info = it->second; + if (info.started) + { + setReturnS32(ctx, 0); + return; + } + + info.started = true; + info.arg = arg; + + // Spawn a host thread to simulate PS2 thread execution. + g_activeThreads.fetch_add(1, std::memory_order_relaxed); + std::thread([=]() mutable { + R5900Context threadCtxCopy = *ctx; // Copy current CPU state to simulate a new thread context + R5900Context *threadCtx = &threadCtxCopy; + + if (info.stack && info.stackSize) + { + SET_GPR_U32(threadCtx, 29, info.stack + info.stackSize); // SP at top of stack + } + if (info.gp) + { + SET_GPR_U32(threadCtx, 28, info.gp); + } + + SET_GPR_U32(threadCtx, 4, info.arg); + threadCtx->pc = info.entry; + + PS2Runtime::RecompiledFunction func = runtime->lookupFunction(info.entry); + g_currentThreadId = tid; + + std::cout << "[StartThread] id=" << tid + << " entry=0x" << std::hex << info.entry + << " sp=0x" << GPR_U32(threadCtx, 29) + << " gp=0x" << GPR_U32(threadCtx, 28) + << " arg=0x" << info.arg << std::dec << std::endl; + + try + { + func(rdram, threadCtx, runtime); + } + catch (const std::exception &e) + { + std::cerr << "[StartThread] id=" << tid << " exception: " << e.what() << std::endl; + } + + std::cout << "[StartThread] id=" << tid << " returned (pc=0x" + << std::hex << threadCtx->pc << std::dec << ")" << std::endl; + + g_activeThreads.fetch_sub(1, std::memory_order_relaxed); + }).detach(); + + // for now report success to the caller. + setReturnS32(ctx, 0); } void ExitThread(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) { - // TODO std::cout << "PS2 ExitThread: Thread is exiting (PC=0x" << std::hex << ctx->pc << std::dec << ")" << std::endl; + setReturnS32(ctx, 0); } void ExitDeleteThread(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) { - // TODO + int tid = static_cast(getRegU32(ctx, 4)); + g_threads.erase(tid); + setReturnS32(ctx, 0); } void TerminateThread(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) { - // TODO + int tid = static_cast(getRegU32(ctx, 4)); + g_threads.erase(tid); + setReturnS32(ctx, 0); } void SuspendThread(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) { - // TODO + setReturnS32(ctx, 0); } void ResumeThread(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) { - // TODO + setReturnS32(ctx, 0); } void GetThreadId(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) { - // TODO + setReturnS32(ctx, g_currentThreadId); } void ReferThreadStatus(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) { - // TODO + setReturnS32(ctx, 0); } void SleepThread(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) { - // TODO + setReturnS32(ctx, 0); } void WakeupThread(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) { - // TODO + setReturnS32(ctx, 0); } void iWakeupThread(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) { - // TODO + setReturnS32(ctx, 0); } void ChangeThreadPriority(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) { - // TODO + int tid = static_cast(getRegU32(ctx, 4)); + int newPrio = static_cast(getRegU32(ctx, 5)); + auto it = g_threads.find(tid); + if (it != g_threads.end()) + { + it->second.priority = newPrio; + } + setReturnS32(ctx, 0); } void RotateThreadReadyQueue(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) { - // TODO + setReturnS32(ctx, 0); } void ReleaseWaitThread(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) { - // TODO + setReturnS32(ctx, 0); } void iReleaseWaitThread(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) { - // TODO + setReturnS32(ctx, 0); } void CreateSema(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) { - // TODO + uint32_t paramAddr = getRegU32(ctx, 4); // $a0 + const uint32_t *param = reinterpret_cast(getConstMemPtr(rdram, paramAddr)); + int init = 0; + int max = 1; + if (param) + { + // sceSemaParam layout commonly: attr(0), option(1), initCount(2), maxCount(3) + init = static_cast(param[2]); + max = static_cast(param[3]); + } + + int id = g_nextSemaId++; + auto info = std::make_shared(); + info->count = init; + info->maxCount = max; + g_semas.emplace(id, info); + std::cout << "[CreateSema] id=" << id << " init=" << init << " max=" << max << std::endl; + setReturnS32(ctx, id); } void DeleteSema(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) { - // TODO + int sid = static_cast(getRegU32(ctx, 4)); + g_semas.erase(sid); + setReturnS32(ctx, 0); } void SignalSema(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) { - // TODO + int sid = static_cast(getRegU32(ctx, 4)); + auto it = g_semas.find(sid); + if (it != g_semas.end()) + { + auto sema = it->second; + std::lock_guard lock(sema->m); + if (sema->count < sema->maxCount) + { + sema->count++; + } + sema->cv.notify_one(); + } + setReturnS32(ctx, 0); } void iSignalSema(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) { - // TODO + SignalSema(rdram, ctx, runtime); } void WaitSema(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) { - // TODO + // For now, never block; treat as immediately acquired. + int sid = static_cast(getRegU32(ctx, 4)); + auto it = g_semas.find(sid); + if (it != g_semas.end()) + { + auto sema = it->second; + std::lock_guard lock(sema->m); + if (sema->count > 0) + { + sema->count--; + } + } + setReturnS32(ctx, 0); } void PollSema(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) { - // TODO + int sid = static_cast(getRegU32(ctx, 4)); + auto it = g_semas.find(sid); + if (it != g_semas.end()) + { + auto sema = it->second; + std::lock_guard lock(sema->m); + if (sema->count > 0) + { + sema->count--; + setReturnS32(ctx, 0); + return; + } + } + setReturnS32(ctx, 0); } void iPollSema(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) { - // TODO + PollSema(rdram, ctx, runtime); } void ReferSemaStatus(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) { - // TODO + setReturnS32(ctx, 0); } void iReferSemaStatus(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) { - // TODO + ReferSemaStatus(rdram, ctx, runtime); } void CreateEventFlag(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime)