#pragma once #include "PPUThread.h" using ppu_function_t = bool(*)(ppu_thread&); // BIND_FUNC macro "converts" any appropriate HLE function to ppu_function_t, binding it to PPU thread context. #define BIND_FUNC(func, ...) (static_cast([](ppu_thread& ppu) -> bool {\ const auto old_f = ppu.last_function;\ ppu.last_function = #func;\ ppu_func_detail::do_call(ppu, func);\ ppu.last_function = old_f;\ ppu.cia += 4;\ __VA_ARGS__;\ return false;\ })) struct ppu_va_args_t { u32 count; // Number of 64-bit args passed }; namespace ppu_func_detail { // argument type classification enum arg_class : u32 { ARG_GENERAL, // argument stored in gpr (from r3 to r10) ARG_FLOAT, // argument stored in fpr (from f1 to f13) ARG_VECTOR, // argument stored in vr (from v2 to v13) ARG_STACK, // argument stored on the stack ARG_CONTEXT, // ppu_thread& passed, doesn't affect g/f/v_count ARG_VARIADIC, // argument count at specific position, doesn't affect g/f/v_count ARG_UNKNOWN, }; template struct bind_arg { static_assert(type == ARG_GENERAL, "Unknown function argument type"); static_assert(!std::is_pointer::value, "Invalid function argument type (pointer)"); static_assert(!std::is_reference::value, "Invalid function argument type (reference)"); static_assert(sizeof(T) <= 8, "Invalid function argument type for ARG_GENERAL"); static inline T get_arg(ppu_thread& ppu) { return ppu_gpr_cast(ppu.gpr[g_count + 2]); } }; template struct bind_arg { static_assert(sizeof(T) <= 8, "Invalid function argument type for ARG_FLOAT"); static inline T get_arg(ppu_thread& ppu) { return static_cast(ppu.fpr[f_count]); } }; template struct bind_arg { static_assert(std::is_same, v128>::value, "Invalid function argument type for ARG_VECTOR"); static FORCE_INLINE T get_arg(ppu_thread& ppu) { return ppu.vr[v_count + 1]; } }; template struct bind_arg { static_assert(alignof(T) <= 16, "Unsupported type alignment for ARG_STACK"); static FORCE_INLINE T get_arg(ppu_thread& ppu) { return ppu_gpr_cast(*ppu.get_stack_arg(g_count, alignof(T))); // TODO } }; template struct bind_arg { static_assert(std::is_same, ppu_thread>::value, "Invalid function argument type for ARG_CONTEXT"); static FORCE_INLINE ppu_thread& get_arg(ppu_thread& ppu) { return ppu; } }; template struct bind_arg { static_assert(std::is_same, ppu_va_args_t>::value, "Invalid function argument type for ARG_VARIADIC"); static FORCE_INLINE ppu_va_args_t get_arg(ppu_thread& ppu) { return{ g_count }; } }; template struct bind_result { static_assert(type == ARG_GENERAL, "Unknown function result type"); static_assert(sizeof(T) <= 8, "Invalid function result type for ARG_GENERAL"); static FORCE_INLINE void put_result(ppu_thread& ppu, const T& result) { ppu.gpr[3] = ppu_gpr_cast(result); } }; template struct bind_result { static_assert(sizeof(T) <= 8, "Invalid function result type for ARG_FLOAT"); static FORCE_INLINE void put_result(ppu_thread& ppu, const T& result) { ppu.fpr[1] = static_cast(result); } }; template struct bind_result { static_assert(std::is_same, v128>::value, "Invalid function result type for ARG_VECTOR"); static FORCE_INLINE void put_result(ppu_thread& ppu, const T& result) { ppu.vr[2] = result; } }; // wrapper for variadic argument info list, each value contains packed argument type and counts of GENERAL, FLOAT and VECTOR arguments template struct arg_info_pack_t; template struct arg_info_pack_t { static const u32 last_value = arg_info_pack_t::last_value; }; template struct arg_info_pack_t { static const u32 last_value = First; }; template<> struct arg_info_pack_t<> { static const u32 last_value = 0; }; // argument type + g/f/v_count unpacker template struct bind_arg_packed { static FORCE_INLINE T get_arg(ppu_thread& ppu) { return bind_arg(type_pack & 0xff), (type_pack >> 8) & 0xff, (type_pack >> 16) & 0xff, (type_pack >> 24)>::get_arg(ppu); } }; template FORCE_INLINE RT call(ppu_thread& ppu, RT(*func)(Args...), arg_info_pack_t) { // do the actual function call when all arguments are prepared (simultaneous unpacking of Args... and Info...) return func(bind_arg_packed::get_arg(ppu)...); } template FORCE_INLINE RT call(ppu_thread& ppu, RT(*func)(Args...), arg_info_pack_t info) { // unpack previous type counts (0/0/0 for the first time) const u32 g_count = (info.last_value >> 8) & 0xff; const u32 f_count = (info.last_value >> 16) & 0xff; const u32 v_count = (info.last_value >> 24); // TODO: check calculations const bool is_float = std::is_floating_point::value; const bool is_vector = std::is_same, v128>::value; const bool is_context = std::is_same, ppu_thread>::value; const bool is_variadic = std::is_same, ppu_va_args_t>::value; const bool is_general = !is_float && !is_vector && !is_context && !is_variadic; const arg_class t = is_general ? (g_count >= 8 ? ARG_STACK : ARG_GENERAL) : is_float ? (f_count >= 13 ? ARG_STACK : ARG_FLOAT) : is_vector ? (v_count >= 12 ? ARG_STACK : ARG_VECTOR) : is_context ? ARG_CONTEXT : is_variadic ? ARG_VARIADIC : ARG_UNKNOWN; const u32 g = g_count + (is_general || is_float ? 1 : is_vector ? ::align(g_count, 2) + 2 : 0); const u32 f = f_count + is_float; const u32 v = v_count + is_vector; return call(ppu, func, arg_info_pack_t{}); } template struct result_type { static_assert(!std::is_pointer::value, "Invalid function result type (pointer)"); static_assert(!std::is_reference::value, "Invalid function result type (reference)"); static const bool is_float = std::is_floating_point::value; static const bool is_vector = std::is_same, v128>::value; static const arg_class value = is_float ? ARG_FLOAT : (is_vector ? ARG_VECTOR : ARG_GENERAL); }; template struct func_binder; template struct func_binder { using func_t = void(*)(T...); static FORCE_INLINE void do_call(ppu_thread& ppu, func_t func) { call(ppu, func, arg_info_pack_t<>{}); } }; template struct func_binder { using func_t = RT(*)(T...); static FORCE_INLINE void do_call(ppu_thread& ppu, func_t func) { bind_result::value>::put_result(ppu, call(ppu, func, arg_info_pack_t<>{})); } }; template FORCE_INLINE void do_call(ppu_thread& ppu, RT(*func)(T...)) { func_binder::do_call(ppu, func); } } class ppu_function_manager { // Global variable for each registered function template struct registered { static u32 index; }; // Access global function list static std::vector& access(); static u32 add_function(ppu_function_t function); public: // Register function (shall only be called during global initialization) template static inline u32 register_function(ppu_function_t func) { return registered::index = add_function(func); } // Get function index template static inline u32 get_index() { return registered::index; } // Read all registered functions static inline const auto& get() { return access(); } // Allocation address static u32 addr; }; template u32 ppu_function_manager::registered::index = 0; #define FIND_FUNC(func) ppu_function_manager::get_index()