diff --git a/External/FEXCore/Source/Interface/HLE/Thunks/Thunks.cpp b/External/FEXCore/Source/Interface/HLE/Thunks/Thunks.cpp index fc835621e..d899b5ce8 100644 --- a/External/FEXCore/Source/Interface/HLE/Thunks/Thunks.cpp +++ b/External/FEXCore/Source/Interface/HLE/Thunks/Thunks.cpp @@ -107,7 +107,9 @@ namespace FEXCore { } }; - class ThunkHandler_impl final: public ThunkHandler { + HostToGuestTrampolinePtr* MakeHostTrampolineForGuestFunction(void* HostPacker, uintptr_t GuestTarget, uintptr_t GuestUnpacker); + + struct ThunkHandler_impl final: public ThunkHandler { std::shared_mutex ThunksMutex; std::unordered_map Thunks = { @@ -132,9 +134,9 @@ namespace FEXCore { &LinkAddressToGuestFunction }, { - // sha256(fex:make_host_trampoline_for_guest_function) - { 0x1e, 0x51, 0x6b, 0x07, 0x39, 0xeb, 0x50, 0x59, 0xb3, 0xf3, 0x4f, 0xca, 0xdd, 0x58, 0x37, 0xe9, 0xf0, 0x30, 0xe5, 0x89, 0x81, 0xc7, 0x14, 0xfb, 0x24, 0xf9, 0xba, 0xe7, 0x0e, 0x00, 0x1e, 0x86 }, - &MakeHostTrampolineForGuestFunction + // sha256(fex:allocate_host_trampoline_for_guest_function) + { 0x9b, 0xb2, 0xf4, 0xb4, 0x83, 0x7d, 0x28, 0x93, 0x40, 0xcb, 0xf4, 0x7a, 0x0b, 0x47, 0x85, 0x87, 0xf9, 0xbc, 0xb5, 0x27, 0xca, 0xa6, 0x93, 0xa5, 0xc0, 0x73, 0x27, 0x24, 0xae, 0xc8, 0xb8, 0x5a }, + &AllocateHostTrampolineForGuestFunction } }; @@ -212,91 +214,24 @@ namespace FEXCore { } /** - * Generates a host-callable trampoline to call guest functions via the host ABI. + * Guest-side helper to initiate creation of a host trampoline for + * calling guest functions. This must be followed by a host-side call + * to FinalizeHostTrampolineForGuestFunction to make the trampoline + * usable. * - * This trampoline uses the same calling convention as the given HostPacker. Trampolines - * are cached, so it's safe to call this function repeatedly on the same arguments without - * leaking memory. - * - * Invoking the returned trampoline has the effect of: - * - packing the arguments (using the HostPacker identified by its SHA256) - * - performing a host->guest transition - * - unpacking the arguments via GuestUnpacker - * - calling the function at GuestTarget - * - * The primary use case of this is ensuring that guest function pointers ("callbacks") - * passed to thunked APIs can safely be called by the native host library. + * This two-step initialization is equivalent to a host-side call to + * MakeHostTrampolineForGuestFunction. The split is needed if the + * host doesn't have all information needed to create the trampoline + * on its own. */ - static void MakeHostTrampolineForGuestFunction(void* ArgsRV) { - struct ArgsRV_t { - IR::SHA256Sum *HostPackerSha256; - uintptr_t GuestUnpacker; - uintptr_t GuestTarget; - uintptr_t rv; // Pointer to host trampoline + TrampolineInstanceInfo - } *args = reinterpret_cast(ArgsRV); + static void AllocateHostTrampolineForGuestFunction(void* ArgsRV) { + struct ArgsRV_t { + uintptr_t GuestUnpacker; + uintptr_t GuestTarget; + uintptr_t rv; // Pointer to host trampoline + TrampolineInstanceInfo + } *args = reinterpret_cast(ArgsRV); - LOGMAN_THROW_AA_FMT(args->GuestTarget, "Tried to create host-trampoline to null pointer guest function"); - - const auto CTX = Thread->CTX; - const auto ThunkHandler = reinterpret_cast(CTX->ThunkHandler.get()); - - const GuestcallInfo gci = { args->GuestUnpacker, args->GuestTarget }; - - // Try first with shared_lock - { - std::shared_lock lk(ThunkHandler->ThunksMutex); - - auto found = ThunkHandler->GuestcallToHostTrampoline.find(gci); - if (found != ThunkHandler->GuestcallToHostTrampoline.end()) { - args->rv = reinterpret_cast(found->second); - return; - } - } - - std::lock_guard lk(ThunkHandler->ThunksMutex); - - // Retry lookup with full lock before making a new trampoline to avoid double trampolines - { - auto found = ThunkHandler->GuestcallToHostTrampoline.find(gci); - if (found != ThunkHandler->GuestcallToHostTrampoline.end()) { - args->rv = reinterpret_cast(found->second); - return; - } - } - - // No entry found => create new trampoline - auto HostPackerEntry = ThunkHandler->Thunks.find(*args->HostPackerSha256); - if (HostPackerEntry == ThunkHandler->Thunks.end()) { - ERROR_AND_DIE_FMT("Unknown host packing function for callback"); - } - - LogMan::Msg::DFmt("Thunks: Adding host trampoline for guest function {:#x}", - args->GuestTarget); - - if (ThunkHandler->HostTrampolineInstanceDataAvailable < HostToGuestTrampolineSize) { - const auto allocation_step = 16 * 1024; - ThunkHandler->HostTrampolineInstanceDataAvailable = allocation_step; - ThunkHandler->HostTrampolineInstanceDataPtr = (uint8_t *)mmap( - 0, ThunkHandler->HostTrampolineInstanceDataAvailable, - PROT_READ | PROT_WRITE | PROT_EXEC, - MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); - - LOGMAN_THROW_AA_FMT(ThunkHandler->HostTrampolineInstanceDataPtr != MAP_FAILED, "Failed to mmap HostTrampolineInstanceDataPtr"); - } - - auto HostTrampoline = reinterpret_cast(ThunkHandler->HostTrampolineInstanceDataPtr); - ThunkHandler->HostTrampolineInstanceDataAvailable -= HostToGuestTrampolineSize; - ThunkHandler->HostTrampolineInstanceDataPtr += HostToGuestTrampolineSize; - memcpy(HostTrampoline, (void*)&HostToGuestTrampolineTemplate, HostToGuestTrampolineSize); - GetInstanceInfo(HostTrampoline) = TrampolineInstanceInfo { - .HostPacker = reinterpret_cast(HostPackerEntry->second), - .CallCallback = (uintptr_t)&CallCallback, - .GuestUnpacker = args->GuestUnpacker, - .GuestTarget = args->GuestTarget - }; - - args->rv = reinterpret_cast(HostTrampoline); - ThunkHandler->GuestcallToHostTrampoline[gci] = HostTrampoline; + args->rv = (uintptr_t)MakeHostTrampolineForGuestFunction(nullptr, args->GuestTarget, args->GuestUnpacker); } /** @@ -378,8 +313,6 @@ namespace FEXCore { } } - public: - ThunkedFunction* LookupThunk(const IR::SHA256Sum &sha256) { std::shared_lock lk(ThunksMutex); @@ -401,4 +334,98 @@ namespace FEXCore { ThunkHandler* ThunkHandler::Create() { return new ThunkHandler_impl(); } + + /** + * Generates a host-callable trampoline to call guest functions via the host ABI. + * + * This trampoline uses the same calling convention as the given HostPacker. Trampolines + * are cached, so it's safe to call this function repeatedly on the same arguments without + * leaking memory. + * + * Invoking the returned trampoline has the effect of: + * - packing the arguments (using the HostPacker identified by its SHA256) + * - performing a host->guest transition + * - unpacking the arguments via GuestUnpacker + * - calling the function at GuestTarget + * + * The primary use case of this is ensuring that guest function pointers ("callbacks") + * passed to thunked APIs can safely be called by the native host library. + * + * Returns a pointer to the generated host trampoline and its TrampolineInstanceInfo. + * + * If HostPacker is zero, the trampoline will be partially initialized and needs to be + * finalized with a call to FinalizeHostTrampolineForGuestFunction. A typical use case + * is to allocate the trampoline for a given GuestTarget/GuestUnpacker on the guest-side, + * and provide the HostPacker host-side. + */ + __attribute__((visibility("default"))) + HostToGuestTrampolinePtr* MakeHostTrampolineForGuestFunction(void* HostPacker, uintptr_t GuestTarget, uintptr_t GuestUnpacker) { + LOGMAN_THROW_AA_FMT(GuestTarget, "Tried to create host-trampoline to null pointer guest function"); + + const auto CTX = Thread->CTX; + const auto ThunkHandler = reinterpret_cast(CTX->ThunkHandler.get()); + + const GuestcallInfo gci = { GuestUnpacker, GuestTarget }; + + // Try first with shared_lock + { + std::shared_lock lk(ThunkHandler->ThunksMutex); + + auto found = ThunkHandler->GuestcallToHostTrampoline.find(gci); + if (found != ThunkHandler->GuestcallToHostTrampoline.end()) { + return found->second; + } + } + + std::lock_guard lk(ThunkHandler->ThunksMutex); + + // Retry lookup with full lock before making a new trampoline to avoid double trampolines + { + auto found = ThunkHandler->GuestcallToHostTrampoline.find(gci); + if (found != ThunkHandler->GuestcallToHostTrampoline.end()) { + return found->second; + } + } + + LogMan::Msg::DFmt("Thunks: Adding host trampoline for guest function {:#x} via unpacker {:#x}", + GuestTarget, GuestUnpacker); + + if (ThunkHandler->HostTrampolineInstanceDataAvailable < HostToGuestTrampolineSize) { + const auto allocation_step = 16 * 1024; + ThunkHandler->HostTrampolineInstanceDataAvailable = allocation_step; + ThunkHandler->HostTrampolineInstanceDataPtr = (uint8_t *)mmap( + 0, ThunkHandler->HostTrampolineInstanceDataAvailable, + PROT_READ | PROT_WRITE | PROT_EXEC, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + + LOGMAN_THROW_AA_FMT(ThunkHandler->HostTrampolineInstanceDataPtr != MAP_FAILED, "Failed to mmap HostTrampolineInstanceDataPtr"); + } + + auto HostTrampoline = reinterpret_cast(ThunkHandler->HostTrampolineInstanceDataPtr); + ThunkHandler->HostTrampolineInstanceDataAvailable -= HostToGuestTrampolineSize; + ThunkHandler->HostTrampolineInstanceDataPtr += HostToGuestTrampolineSize; + memcpy(HostTrampoline, (void*)&HostToGuestTrampolineTemplate, HostToGuestTrampolineSize); + GetInstanceInfo(HostTrampoline) = TrampolineInstanceInfo { + .HostPacker = HostPacker, + .CallCallback = (uintptr_t)&ThunkHandler_impl::CallCallback, + .GuestUnpacker = GuestUnpacker, + .GuestTarget = GuestTarget + }; + + ThunkHandler->GuestcallToHostTrampoline[gci] = HostTrampoline; + return HostTrampoline; + } + + __attribute__((visibility("default"))) + void FinalizeHostTrampolineForGuestFunction(HostToGuestTrampolinePtr* TrampolineAddress, void* HostPacker) { + auto& Trampoline = GetInstanceInfo(TrampolineAddress); + + LOGMAN_THROW_A_FMT(Trampoline.CallCallback == (uintptr_t)&ThunkHandler_impl::CallCallback, + "Invalid trampoline at {} passed to {}", fmt::ptr(TrampolineAddress), __FUNCTION__); + + if (!Trampoline.HostPacker) { + LogMan::Msg::DFmt("Thunks: Finalizing trampoline at {} with host packer {}", fmt::ptr(TrampolineAddress), fmt::ptr(HostPacker)); + Trampoline.HostPacker = HostPacker; + } + } } diff --git a/Source/Tests/CMakeLists.txt b/Source/Tests/CMakeLists.txt index c4dbc54c9..7ca143bc0 100644 --- a/Source/Tests/CMakeLists.txt +++ b/Source/Tests/CMakeLists.txt @@ -6,6 +6,9 @@ add_executable(FEXLoader FEXLoader.cpp AOT/AOTGenerator.cpp) +# Enable FEX APIs to be used by targets that use target_link_libraries on FEXLoader +set_target_properties(FEXLoader PROPERTIES ENABLE_EXPORTS 1) + target_include_directories(FEXLoader PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/Source/ diff --git a/ThunkLibs/Generator/gen.cpp b/ThunkLibs/Generator/gen.cpp index 665dba746..507f1f259 100644 --- a/ThunkLibs/Generator/gen.cpp +++ b/ThunkLibs/Generator/gen.cpp @@ -422,12 +422,8 @@ static std::string format_function_args(const FunctionParams& params, Fn&& forma return ret; }; -static std::string format_function_args(const FunctionParams& params) { - return format_function_args(params, - [](std::size_t idx) -> std::string { return "args->a_" + std::to_string(idx); }); -} - void GenerateThunkLibsAction::EndSourceFileAction() { + static auto format_decl = [](clang::QualType type, const std::string_view& name) { if (type->isFunctionPointerType()) { auto signature = type.getAsString(); @@ -491,22 +487,6 @@ void GenerateThunkLibsAction::EndSourceFileAction() { first = false; } file << "\")\n"; - - // Generate SHA256 sums for automatically handled callbacks - for (auto& [cb_idx, cb] : thunk.callbacks) { - if (!cb.is_stub && !cb.is_guest) { - bool is_first_cb = (cb_idx == thunk.callbacks.begin()->first); - auto cb_function_name = get_callback_name(function_name, cb_idx, is_first_cb); - - auto cb_sha256 = get_sha256(cb_function_name); - - file << "static uint8_t fexcallback_" << libname << "_" << cb_function_name << "[32] = { "; - for (auto c : cb_sha256) { - file << "0x" << std::hex << std::setw(2) << std::setfill('0') << +c << ", "; - } - file << "};\n"; - } - } } file << "}\n"; @@ -524,6 +504,7 @@ void GenerateThunkLibsAction::EndSourceFileAction() { cb_sha256_str.pop_back(); // Thunk used for guest-side calls to host function pointers + file << " // " << funcptr_signature << "\n"; auto funcptr_idx = std::distance(funcptr_types.begin(), type_it); file << " MAKE_CALLBACK_THUNK(callback_" << funcptr_idx << ", " << funcptr_signature << ", \"" << cb_sha256_str << "\");\n"; } @@ -594,6 +575,7 @@ void GenerateThunkLibsAction::EndSourceFileAction() { file << " char force_nonempty;\n"; } file << " } args;\n"; + for (std::size_t idx = 0; idx < data.param_types.size(); ++idx) { auto cb = data.callbacks.find(idx); @@ -602,9 +584,7 @@ void GenerateThunkLibsAction::EndSourceFileAction() { file << "a_" << idx << ";\n"; } else { // Before passing guest function pointers to the host, wrap them in a host-callable trampoline - bool is_first_cb = (cb->first == data.callbacks.begin()->first); - auto cb_name = get_callback_name(function_name, cb->first, is_first_cb); - file << "MakeHostTrampolineForGuestFunction(fexcallback_" << libname << "_" << cb_name << ", &fexfn_unpack_" << libname << "_" << cb_name << ", a_" << std::to_string(idx) << ");\n"; + file << "AllocateHostTrampolineForGuestFunction(a_" << std::to_string(idx) << ");\n"; } } file << " fexthunks_" << libname << "_" << function_name << "(&args);\n"; @@ -650,28 +630,6 @@ void GenerateThunkLibsAction::EndSourceFileAction() { file << " fprintf(stderr, \"FATAL: Attempted to invoke callback stub for " << function_name << "\\n\");\n"; file << " std::abort();\n"; file << "}\n"; - } else if (!cb.is_guest) { - bool is_first_cb = (cb_idx == thunk.callbacks.begin()->first); - const char* variadic_ellipsis = cb.is_variadic ? ", ..." : ""; - auto cb_function_name = get_callback_name(function_name, cb_idx, is_first_cb); - - file << "static " << cb.return_type.getAsString() << " fexfn_pack_guestcall_" - << cb_function_name << "(" - << format_function_params(cb) << variadic_ellipsis << ") {\n"; - file << " GuestcallInfo *guestcall;\n"; - file << " LOAD_INTERNAL_GUESTPTR_VIA_CUSTOM_ABI(guestcall);\n"; - - auto args_struct_name = GeneratePackedArgs(cb_function_name, cb); - file << " " << args_struct_name << " argsrv;\n"; - for (std::size_t idx = 0; idx < cb.param_types.size(); ++idx) { - file << " argsrv.a_" << idx << " = a_" << idx << ";\n"; - } - file << " guestcall->CallCallback(guestcall->GuestUnpacker, guestcall->GuestTarget, &argsrv);\n"; - - if (!cb.return_type->isVoidType()) { - file << " return argsrv.rv;\n"; - } - file << "}\n"; } } @@ -693,6 +651,11 @@ void GenerateThunkLibsAction::EndSourceFileAction() { return "fexfn_unpack_" + get_callback_name(function_name, cb->first, is_first_cb) + "_stub"; } else if (cb != thunk.callbacks.end() && cb->second.is_guest) { return "fex_guest_function_ptr { args->a_" + std::to_string(idx) + " }"; + } else if (cb != thunk.callbacks.end()) { + auto arg_name = "args->a_" + std::to_string(idx); + // Use comma operator to inject a function call before returning the argument + return "(FinalizeHostTrampolineForGuestFunction(" + arg_name + "), " + arg_name + ")"; + } else { return "args->a_" + std::to_string(idx); } @@ -719,21 +682,6 @@ void GenerateThunkLibsAction::EndSourceFileAction() { file << "\\x" << std::hex << std::setw(2) << std::setfill('0') << +c; } file << "\", (void(*)(void *))&fexfn_unpack_" << libname << "_" << function_name << "}, // " << libname << ":" << function_name << "\n"; - - for (auto& [cb_idx, cb] : thunk.callbacks) { - if (cb.is_stub || cb.is_guest) - continue; - bool is_first_cb = (cb_idx == thunk.callbacks.begin()->first); - auto cb_function_name = get_callback_name(function_name, cb_idx, is_first_cb); - - auto cb_sha256 = get_sha256(cb_function_name); - - file << "{(uint8_t*)\""; - for (auto c : cb_sha256) { - file << "\\x" << std::hex << std::setw(2) << std::setfill('0') << +c; - } - file << "\", (void(*)(void *))&fexfn_pack_guestcall_" << cb_function_name << "}, // " << libname << ":" << cb_function_name << "\n"; - } } for (auto& type : funcptr_types) { @@ -784,31 +732,8 @@ void GenerateThunkLibsAction::EndSourceFileAction() { } if (!output_filenames.callback_unpacks.empty()) { + // TODO: Not needed anymore std::ofstream file(output_filenames.callback_unpacks); - - for (auto& thunk : thunks) { - for (const auto& [cb_idx, cb] : thunk.callbacks) { - if (cb.is_stub || cb.is_guest) { - continue; - } - - bool is_void = cb.return_type->isVoidType(); - bool is_first_cb = (cb_idx == thunk.callbacks.begin()->first); - auto cb_function_name = get_callback_name(thunk.function_name, cb_idx, is_first_cb); - file << "static void fexfn_unpack_" << libname << "_" << cb_function_name << "(uintptr_t cb, void* argsv) {\n"; - file << " typedef " << cb.return_type.getAsString() << " fn_t (" << format_function_params(cb) << ");\n"; - file << " auto callback = reinterpret_cast(cb);\n"; - file << " struct arg_t {\n"; - file << format_struct_members(cb, " "); - if (!is_void) { - file << " " << format_decl(cb.return_type, "rv") << ";\n"; - } - file << " };\n"; - file << " auto args = (arg_t*)argsv;\n"; - file << (is_void ? " " : " args->rv = ") << "callback(" << format_function_args(cb) << ");\n"; - file << "}\n"; - } - } } if (!output_filenames.symbol_list.empty()) { diff --git a/ThunkLibs/HostLibs/CMakeLists.txt b/ThunkLibs/HostLibs/CMakeLists.txt index 9b55fc955..771d5e26b 100644 --- a/ThunkLibs/HostLibs/CMakeLists.txt +++ b/ThunkLibs/HostLibs/CMakeLists.txt @@ -13,6 +13,7 @@ function(generate NAME SOURCE_FILE) # Interface target for the user to add include directories add_library(${NAME}-deps INTERFACE) target_include_directories(${NAME}-deps INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/../include") + target_link_libraries(${NAME}-deps INTERFACE FEXLoader) # Shorthand for the include directories added after calling this function. # This is not evaluated directly, hence directories added after return are still picked up set(prop "$") diff --git a/ThunkLibs/include/common/Guest.h b/ThunkLibs/include/common/Guest.h index 525f583c1..00c1f07f1 100644 --- a/ThunkLibs/include/common/Guest.h +++ b/ThunkLibs/include/common/Guest.h @@ -49,7 +49,7 @@ MAKE_THUNK(fex, loadlib, "0x27, 0x7e, 0xb7, 0x69, 0x5b, 0xe9, 0xab, 0x12, 0x6e, MAKE_THUNK(fex, is_lib_loaded, "0xee, 0x57, 0xba, 0x0c, 0x5f, 0x6e, 0xef, 0x2a, 0x8c, 0xb5, 0x19, 0x81, 0xc9, 0x23, 0xe6, 0x51, 0xae, 0x65, 0x02, 0x8f, 0x2b, 0x5d, 0x59, 0x90, 0x6a, 0x7e, 0xe2, 0xe7, 0x1c, 0x33, 0x8a, 0xff") MAKE_THUNK(fex, is_host_heap_allocation, "0xf5, 0x77, 0x68, 0x43, 0xbb, 0x6b, 0x28, 0x18, 0x40, 0xb0, 0xdb, 0x8a, 0x66, 0xfb, 0x0e, 0x2d, 0x98, 0xc2, 0xad, 0xe2, 0x5a, 0x18, 0x5a, 0x37, 0x2e, 0x13, 0xc9, 0xe7, 0xb9, 0x8c, 0xa9, 0x3e") MAKE_THUNK(fex, link_address_to_function, "0xe6, 0xa8, 0xec, 0x1c, 0x7b, 0x74, 0x35, 0x27, 0xe9, 0x4f, 0x5b, 0x6e, 0x2d, 0xc9, 0xa0, 0x27, 0xd6, 0x1f, 0x2b, 0x87, 0x8f, 0x2d, 0x35, 0x50, 0xea, 0x16, 0xb8, 0xc4, 0x5e, 0x42, 0xfd, 0x77") -MAKE_THUNK(fex, make_host_trampoline_for_guest_function, "0x1e, 0x51, 0x6b, 0x07, 0x39, 0xeb, 0x50, 0x59, 0xb3, 0xf3, 0x4f, 0xca, 0xdd, 0x58, 0x37, 0xe9, 0xf0, 0x30, 0xe5, 0x89, 0x81, 0xc7, 0x14, 0xfb, 0x24, 0xf9, 0xba, 0xe7, 0x0e, 0x00, 0x1e, 0x86") +MAKE_THUNK(fex, allocate_host_trampoline_for_guest_function, "0x9b, 0xb2, 0xf4, 0xb4, 0x83, 0x7d, 0x28, 0x93, 0x40, 0xcb, 0xf4, 0x7a, 0x0b, 0x47, 0x85, 0x87, 0xf9, 0xbc, 0xb5, 0x27, 0xca, 0xa6, 0x93, 0xa5, 0xc0, 0x73, 0x27, 0x24, 0xae, 0xc8, 0xb8, 0x5a") #define LOAD_LIB_BASE(name, init_fn) \ __attribute__((constructor)) static void loadlib() \ @@ -118,23 +118,45 @@ static auto GetCallerForHostFunction(Result (*host_func)(Args...)) } template -inline Target *MakeHostTrampolineForGuestFunction(uint8_t HostPacker[32], void (*GuestUnpacker)(uintptr_t, void*), Target *GuestTarget) { +inline Target *AllocateHostTrampolineForGuestFunction(void (*GuestUnpacker)(uintptr_t, void*), Target *GuestTarget) { if (!GuestTarget) { return nullptr; } struct { - void *HostPacker; uintptr_t GuestUnpacker; uintptr_t GuestTarget; uintptr_t rv; - } argsrv = { HostPacker, (uintptr_t)GuestUnpacker, (uintptr_t)GuestTarget }; + } argsrv = { (uintptr_t)GuestUnpacker, (uintptr_t)GuestTarget }; - fexthunks_fex_make_host_trampoline_for_guest_function((void*)&argsrv); + fexthunks_fex_allocate_host_trampoline_for_guest_function((void*)&argsrv); return (Target *)argsrv.rv; } +template +struct CallbackUnpack; + +template +struct CallbackUnpack { + static void Unpack(uintptr_t cb, void* argsv) { + using fn_t = Result(Args...); + auto callback = reinterpret_cast(cb); + auto args = reinterpret_cast*>(argsv); + Invoke(callback, *args); + } +}; + +template +struct CallbackUnpack : CallbackUnpack { +}; + +template +inline Target *AllocateHostTrampolineForGuestFunction(Target *GuestTarget) { + return AllocateHostTrampolineForGuestFunction(CallbackUnpack::Unpack, + GuestTarget); +} + inline bool IsHostHeapAllocation(void* ptr) { struct { void* ptr; diff --git a/ThunkLibs/include/common/Host.h b/ThunkLibs/include/common/Host.h index 568d8d96e..d9b24f655 100644 --- a/ThunkLibs/include/common/Host.h +++ b/ThunkLibs/include/common/Host.h @@ -5,10 +5,35 @@ $end_info$ */ #pragma once -#include +#include +#include +#include #include "PackedArguments.h" +// Import FEXCore functions for use in host thunk libraries. +// +// Note these are statically linked into the FEX executable. The linker hence +// doesn't know about them when linking thunk libraries. This issue is avoided +// by declaring the functions as weak symbols. Their implementation in this +// file serves as a panicking fallback if matching symbols are not found. +namespace FEXCore { + struct HostToGuestTrampolinePtr; + + __attribute__((weak)) + HostToGuestTrampolinePtr* + MakeHostTrampolineForGuestFunction(void* HostPacker, uintptr_t GuestTarget, uintptr_t GuestUnpacker) { + fprintf(stderr, "Failed to load %s from FEX executable\n", __FUNCTION__); + std::abort(); + } + __attribute__((weak)) + HostToGuestTrampolinePtr* + FinalizeHostTrampolineForGuestFunction(HostToGuestTrampolinePtr*, void* HostPacker) { + fprintf(stderr, "Failed to load %s from FEX executable\n", __FUNCTION__); + std::abort(); + } +} + template struct function_traits; template @@ -47,7 +72,7 @@ public: #define EXPORTS(name) \ extern "C" { \ - ExportEntry* fexthunks_exports_##name() { \ + ExportEntry* fexthunks_exports_##name(uintptr_t allocate, uintptr_t finalize) { \ if (!fexldr_init_##name()) { \ return nullptr; \ } \ @@ -77,6 +102,9 @@ struct GuestcallInfo { #elif defined(_M_ARM_64) #define LOAD_INTERNAL_GUESTPTR_VIA_CUSTOM_ABI(target_variable) \ asm volatile("mov %0, x11" : "=r" (target_variable)) +#else +#define LOAD_INTERNAL_GUESTPTR_VIA_CUSTOM_ABI(target_variable) \ + abort() #endif template @@ -84,6 +112,19 @@ struct CallbackUnpack; template struct CallbackUnpack { + static Result CallGuestPtr(Args... args) { + GuestcallInfo *guestcall; + LOAD_INTERNAL_GUESTPTR_VIA_CUSTOM_ABI(guestcall); + + PackedArguments packed_args = { + args... + }; + guestcall->CallCallback(guestcall->GuestUnpacker, guestcall->GuestTarget, &packed_args); + if constexpr (!std::is_void_v) { + return packed_args.rv; + } + } + static void ForIndirectCall(void* argsv) { auto args = reinterpret_cast*>(argsv); constexpr auto CBIndex = sizeof...(Args); @@ -133,6 +174,17 @@ struct CallbackUnpack { } }; -template -struct CallbackUnpack : CallbackUnpack { -}; +template +void MakeHostTrampolineForGuestFunctionAt(uintptr_t GuestTarget, uintptr_t GuestUnpacker, FuncType **Func) { + *Func = (FuncType*)FEXCore::MakeHostTrampolineForGuestFunction( + (void*)&CallbackUnpack::CallGuestPtr, + GuestTarget, + GuestUnpacker); +} + +template +void FinalizeHostTrampolineForGuestFunction(F* PreallocatedTrampolineForGuestFunction) { + FEXCore::FinalizeHostTrampolineForGuestFunction( + (FEXCore::HostToGuestTrampolinePtr*)PreallocatedTrampolineForGuestFunction, + (void*)&CallbackUnpack::CallGuestPtr); +} diff --git a/unittests/ThunkLibs/generator.cpp b/unittests/ThunkLibs/generator.cpp index dd37ddcdd..8c285d178 100644 --- a/unittests/ThunkLibs/generator.cpp +++ b/unittests/ThunkLibs/generator.cpp @@ -285,7 +285,9 @@ SourceWithAST Fixture::run_thunkgen_guest(std::string_view prelude, std::string_ "#define MAKE_CALLBACK_THUNK(name, sig, hash) template<> struct callback_thunk_defined {};\n" "#define FEX_PACKFN_LINKAGE\n" "template\n" - "Target *MakeHostTrampolineForGuestFunction(uint8_t HostPacker[32], void (*)(uintptr_t, void*), Target*);\n"; + "Target *MakeHostTrampolineForGuestFunction(uint8_t HostPacker[32], void (*)(uintptr_t, void*), Target*);\n" + "template\n" + "Target *AllocateHostTrampolineForGuestFunction(Target*);\n"; for (auto& filename : { output_filenames.thunks, output_filenames.callback_unpacks, @@ -334,7 +336,9 @@ SourceWithAST Fixture::run_thunkgen_host(std::string_view prelude, std::string_v "template\n" "struct CallbackUnpack {\n" " static void ForIndirectCall(void* argsv);\n" - "};\n"; + "};\n" + "template\n" + "void FinalizeHostTrampolineForGuestFunction(F*);\n"; for (auto& filename : { output_filenames.ldr_ptrs, @@ -459,17 +463,24 @@ TEST_CASE_METHOD(Fixture, "FunctionPointerParameter") { hasName("fexfn_pack_func"), returns(asString("void")), parameterCountIs(1), - hasParameter(0, hasType(asString("int (*)(char, char)"))), - - // Should call MakeHostTrampolineForGuestFunction - hasDescendant(callExpr(callee(functionDecl(hasName("MakeHostTrampolineForGuestFunction"))))) + hasParameter(0, hasType(asString("int (*)(char, char)"))) ))); + // Host packing function should call FinalizeHostTrampolineForGuestFunction on the argument + CHECK_THAT(output.host, + matches(functionDecl( + hasName("fexfn_unpack_libtest_func"), + hasDescendant(callExpr(callee(functionDecl(hasName("FinalizeHostTrampolineForGuestFunction"))), hasArgument(0, expr().bind("funcptr")))) + )).check_binding("funcptr", +[](const clang::Expr* funcptr) { + // Check that the argument type matches the function pointer + return funcptr->getType().getAsString() == "int (*)(char, char)"; + })); + // Host should export the unpacking function for function pointer arguments CHECK_THAT(output.host, matches(varDecl( hasName("exports"), - hasType(constantArrayType(hasElementType(asString("struct ExportEntry")), hasSize(4))), + hasType(constantArrayType(hasElementType(asString("struct ExportEntry")), hasSize(3))), hasInitializer(hasDescendant(declRefExpr(to(cxxMethodDecl(hasName("ForIndirectCall"), ofClass(hasName("CallbackUnpack"))))))) ))); }