#pragma once #include "memory.h" #include "ppc_runtime.h" #include "system_bridge.h" #include "game_graphics_options.h" #include "runtime_log.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "guest_abi/dispatch.h" void InitializePersistentCpuContext(); std::optional FindNearestTranslatedFunction(uint32_t address); // Look up a registered function by host (native) address. // This enables stack trace symbolication for translated functions. std::optional FindTranslatedFunctionByHostAddress(uintptr_t hostAddr); template class AbiTrampoline; template class AbiTrampoline { public: using TargetFn = Ret (*)(Args...); AbiTrampoline(uint32_t address, const char* name, TargetFn target, FunctionKind kind = FunctionKind::BaseTranslated, bool preservesNonvolatileFprs = false, uint32_t nonvolatileFprWriteMask = kPpcAllNonvolatileFprMask, uint32_t priority = 0, uint64_t moduleId = 0, void (*rawCpuInvoker)(CpuContext*) = nullptr, bool mustRemainDynamicallyDispatchable = true) { TranslatedFunctionInfo info; info.address = address; info.name = name ? name : ""; info.moduleId = moduleId; info.priority = priority; const bool effectivePreservesNonvolatileFprs = preservesNonvolatileFprs || kind == FunctionKind::Native; info.nonvolatileFprWriteMask = effectivePreservesNonvolatileFprs ? 0 : (nonvolatileFprWriteMask & kPpcAllNonvolatileFprMask); info.entryPoint = reinterpret_cast(target); info.rawCpuInvoker = rawCpuInvoker; info.mustRemainDynamicallyDispatchable = mustRemainDynamicallyDispatchable; info.kind = kind; TranslatedFunctionRegistry::Register(std::move(info)); } static std::tuple BuildArgs(CpuContext* cpu) { size_t gprIndex = 0; size_t fprIndex = 0; return std::tuple{LoadArgument(cpu, gprIndex, fprIndex)...}; } template static std::remove_reference_t LoadArgument(CpuContext* cpu, size_t& gprIndex, size_t& fprIndex) { using CleanT = std::remove_reference_t; if constexpr (std::is_floating_point_v) { if (fprIndex >= 13) { throw std::out_of_range("FPR argument overflow"); } return static_cast(cpu->fpr[1 + fprIndex++].d); } else { if (gprIndex >= 8) { size_t stackArgIndex = gprIndex - 8; uint32_t sp = cpu->gpr[1]; uint32_t argAddr = sp + 8 + static_cast(stackArgIndex * 4); gprIndex++; try { return static_cast(Memory::Read32(argAddr)); } catch (const Memory::AccessViolation&) { RT_LOG(RT_TAG_RUNTIME) << "Stack parameter read failed at 0x" << std::hex << argAddr << " (SP=0x" << sp << ", argIndex=" << std::dec << (gprIndex - 1) << ")" << std::endl; throw; } } return static_cast(cpu->gpr[3 + gprIndex++]); } } static void InvokeCpu(TargetFn target, CpuContext* cpu) { auto args = BuildArgs(cpu); if constexpr (std::is_void_v) { std::apply(target, args); } else { auto result = std::apply(target, args); if constexpr (std::is_floating_point_v) { cpu->fpr[1].d = static_cast(result); } else { cpu->gpr[3] = static_cast(result); } } } }; template struct AbiRawCpuThunk { static void Invoke(CpuContext* cpu) { if constexpr (std::is_invocable_v) { (void)Target(cpu); } else { AbiTrampoline>::InvokeCpu(Target, cpu); } } }; template class AbiTrampoline { public: using TargetFn = Ret (*)(CpuContext*); AbiTrampoline(uint32_t address, const char* name, TargetFn target, FunctionKind kind = FunctionKind::BaseTranslated, bool preservesNonvolatileFprs = false, uint32_t nonvolatileFprWriteMask = kPpcAllNonvolatileFprMask, uint32_t priority = 0, uint64_t moduleId = 0, void (*rawCpuInvoker)(CpuContext*) = nullptr, bool mustRemainDynamicallyDispatchable = true) { TranslatedFunctionInfo info; info.address = address; info.name = name ? name : ""; info.moduleId = moduleId; info.priority = priority; // Deliberately *not* mirroring the typed specialization's // `|| kind == FunctionKind::Native`. A CpuContext* native receives the // whole guest register file and can legitimately write f14-f31 through // it, so it stays conservative until the individual address has been // measured clean and its registration opts out below. info.nonvolatileFprWriteMask = preservesNonvolatileFprs ? 0 : (nonvolatileFprWriteMask & kPpcAllNonvolatileFprMask); info.entryPoint = reinterpret_cast(target); info.rawCpuInvoker = rawCpuInvoker ? rawCpuInvoker : reinterpret_cast(target); info.mustRemainDynamicallyDispatchable = mustRemainDynamicallyDispatchable; info.kind = kind; TranslatedFunctionRegistry::Register(std::move(info)); } }; #define MKW_DETAIL_CAT(a, b) a##b #define MKW_DETAIL_MAKE_UNIQUE(a, b) MKW_DETAIL_CAT(a, b) // Three hand-written registration macros. Generated code never registers per function; every // translated function reaches the registry via the bulk BulkTranslatedFunctionRecord tables // in the translator's *_registration TUs. #define REGISTER_TRANSLATED_FUNCTION(address, fn) \ static AbiTrampoline MKW_DETAIL_MAKE_UNIQUE(_abi_trampoline_, __COUNTER__)(address, #fn, fn, FunctionKind::BaseTranslated, false, kPpcAllNonvolatileFprMask, 0, 0, nullptr, KnownTranslatedCpuCall
::kMustRemainDynamicallyDispatchable) #define REGISTER_NATIVE_FUNCTION(address, fn) \ static AbiTrampoline MKW_DETAIL_MAKE_UNIQUE(_abi_native_trampoline_, __COUNTER__)(address, #fn, fn, FunctionKind::Native, false, kPpcAllNonvolatileFprMask, 0, 0, &AbiRawCpuThunk<&fn>::Invoke) #define REGISTER_NATIVE_FUNCTION_AS(address, fn, pretty_name) \ static AbiTrampoline MKW_DETAIL_MAKE_UNIQUE(_abi_native_trampoline_named_, __COUNTER__)(address, pretty_name, fn, FunctionKind::Native, false, kPpcAllNonvolatileFprMask, 0, 0, &AbiRawCpuThunk<&fn>::Invoke)