diff --git a/ps2xRecomp/src/lib/function_emitter.cpp b/ps2xRecomp/src/lib/function_emitter.cpp index 328c0e01a..f4a80b4db 100644 --- a/ps2xRecomp/src/lib/function_emitter.cpp +++ b/ps2xRecomp/src/lib/function_emitter.cpp @@ -40,20 +40,6 @@ namespace ps2recomp std::stringstream ss; cg.m_currentFunctionName = function.name; - if (useHeaders) - { - ss << "#include \n"; - ss << "#include \"ps2_runtime_macros.h\"\n"; - ss << "#include \"ps2_runtime.h\"\n"; - ss << "#include \"ps2_recompiled_functions.h\"\n"; - ss << "#include \"ps2_recompiled_stubs.h\"\n\n"; - ss << "#include \"ps2_syscalls.h\"\n"; - ss << "#include \"ps2_stubs.h\"\n\n"; - ss << "#ifdef PS2_FUNCTION_LOG_TRACKER\n"; - ss << "#include \"ps2_log.h\"\n"; - ss << "#endif\n\n"; - } - CodeGenerator::AnalysisResult analysisResult = cg.collectInternalBranchTargets(function, instructions); std::vector resumeTargets(analysisResult.resumeEntryPoints.begin(), analysisResult.resumeEntryPoints.end()); @@ -234,6 +220,50 @@ namespace ps2recomp } ss << "}\n"; - return ss.str(); + + const std::string body = ss.str(); + if (!useHeaders) + { + return body; + } + + std::stringstream output; + output << "#include \n"; + output << "#include \"ps2_runtime_macros.h\"\n"; + output << "#include \"ps2_runtime.h\"\n"; + output << "#include \"ps2_syscalls.h\"\n"; + output << "#include \"ps2_stubs.h\"\n\n"; + output << "#ifdef PS2_FUNCTION_LOG_TRACKER\n"; + output << "#include \"ps2_log.h\"\n"; + output << "#endif\n\n"; + + // Most guest calls are dispatched through PS2Runtime and therefore do + // not need declarations for every generated function. A direct J to + // a recovered function is emitted as a C++ tail call, so declare only + // those targets locally instead of including the global declaration + // header in every generated source file. + constexpr std::string_view callSuffix = "(rdram, ctx, runtime); return;"; + std::unordered_set directJumpTargets; + size_t searchOffset = 0; + while ((searchOffset = body.find(callSuffix, searchOffset)) != std::string::npos) + { + const size_t lineStart = body.rfind('\n', searchOffset); + const size_t nameStart = body.find_first_not_of(" \t", lineStart == std::string::npos ? 0 : lineStart + 1); + if (nameStart != std::string::npos && nameStart < searchOffset) + { + directJumpTargets.emplace(body.substr(nameStart, searchOffset - nameStart)); + } + searchOffset += callSuffix.size(); + } + for (const std::string &target : directJumpTargets) + { + output << "void " << target << "(uint8_t*, R5900Context*, PS2Runtime*);\n"; + } + if (!directJumpTargets.empty()) + { + output << "\n"; + } + output << body; + return output.str(); } } diff --git a/ps2xRecomp/src/lib/ps2_recompiler.cpp b/ps2xRecomp/src/lib/ps2_recompiler.cpp index ab779a4f5..0f8a0fc03 100644 --- a/ps2xRecomp/src/lib/ps2_recompiler.cpp +++ b/ps2xRecomp/src/lib/ps2_recompiler.cpp @@ -102,7 +102,6 @@ namespace ps2recomp void writeCombinedOutputPreamble(std::ostream &output) { output << "#include \n"; - output << "#include \"ps2_recompiled_functions.h\"\n\n"; output << "#include \"ps2_runtime_macros.h\"\n"; output << "#include \"ps2_runtime.h\"\n"; output << "#include \"ps2_recompiled_stubs.h\"\n"; diff --git a/ps2xTest/src/code_generator_tests.cpp b/ps2xTest/src/code_generator_tests.cpp index 1a1f37025..da1c7319c 100644 --- a/ps2xTest/src/code_generator_tests.cpp +++ b/ps2xTest/src/code_generator_tests.cpp @@ -168,6 +168,48 @@ void register_code_generator_tests() { MiniTest::Case("CodeGenerator", [](TestCase &tc) { + tc.Run("function sources avoid the global generated declaration header", [](TestCase &t) { + Function func; + func.name = "isolated_function"; + func.start = 0x8000; + func.end = 0x8004; + func.isRecompiled = true; + + CodeGenerator gen({}, {}); + const std::string generated = gen.generateFunction(func, {makeNop(0x8000)}, true); + + t.IsTrue(generated.find("#include \"ps2_runtime.h\"") != std::string::npos, + "generated functions still require the runtime interface"); + t.IsTrue(generated.find("#include \"ps2_recompiled_functions.h\"") == std::string::npos, + "only the registration unit should depend on all generated declarations"); + t.IsTrue(generated.find("#include \"ps2_recompiled_stubs.h\"") == std::string::npos, + "ordinary generated functions should not depend on every stub declaration"); + }); + + tc.Run("direct generated jumps declare only their target", [](TestCase &t) { + Function func; + func.name = "direct_jump_source"; + func.start = 0x8000; + func.end = 0x8008; + func.isRecompiled = true; + + Instruction jump{}; + jump.address = 0x8000; + jump.opcode = OPCODE_J; + jump.target = (0x9000u >> 2) & 0x3FFFFFFu; + jump.hasDelaySlot = true; + jump.raw = (OPCODE_J << 26) | jump.target; + + CodeGenerator gen({}, {}); + gen.setRenamedFunctions({{0x9000u, "direct_jump_target"}}); + const std::string generated = gen.generateFunction(func, {jump, makeNop(0x8004)}, true); + + t.IsTrue(generated.find("void direct_jump_target(uint8_t*, R5900Context*, PS2Runtime*);") != std::string::npos, + "a direct generated jump should receive a local forward declaration"); + t.IsTrue(generated.find("#include \"ps2_recompiled_functions.h\"") == std::string::npos, + "a direct generated jump should not restore the global declaration dependency"); + }); + tc.Run("SYSCALL publishes its continuation before entering the runtime", [](TestCase &t) { Function func; func.name = "syscall_resume";