diff --git a/External/FEXCore/Source/Interface/HLE/Thunks/Thunks.cpp b/External/FEXCore/Source/Interface/HLE/Thunks/Thunks.cpp index 464ced06a..a825d29bc 100644 --- a/External/FEXCore/Source/Interface/HLE/Thunks/Thunks.cpp +++ b/External/FEXCore/Source/Interface/HLE/Thunks/Thunks.cpp @@ -68,12 +68,23 @@ namespace FEXCore { struct ExportEntry { uint8_t *sha256; ThunkedFunction* Fn; }; struct TrampolineInstanceInfo { - uintptr_t HostPacker; + void* HostPacker; uintptr_t CallCallback; uintptr_t GuestUnpacker; uintptr_t GuestTarget; }; + // Opaque type pointing to an instance of HostToGuestTrampolineTemplate and its + // embedded TrampolineInstanceInfo + struct HostToGuestTrampolinePtr; + const auto HostToGuestTrampolineSize = __stop_HostToGuestTrampolineTemplate - __start_HostToGuestTrampolineTemplate; + + static TrampolineInstanceInfo& GetInstanceInfo(HostToGuestTrampolinePtr* Trampoline) { + const auto Length = __stop_HostToGuestTrampolineTemplate - __start_HostToGuestTrampolineTemplate; + const auto InstanceInfoOffset = Length - sizeof(TrampolineInstanceInfo); + return *reinterpret_cast(reinterpret_cast(Trampoline) + InstanceInfoOffset); + } + struct GuestcallInfo { uintptr_t GuestUnpacker; uintptr_t GuestTarget; @@ -96,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 = { @@ -121,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 } }; @@ -131,7 +144,7 @@ namespace FEXCore { // Ideally we track when a library has been unloaded and remove it from this set before the memory backing goes away. std::set Libs; - std::unordered_map GuestcallToHostTrampoline; + std::unordered_map GuestcallToHostTrampoline; uint8_t *HostTrampolineInstanceDataPtr; size_t HostTrampolineInstanceDataAvailable = 0; @@ -201,98 +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 = 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 = 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); - - const auto Length = __stop_HostToGuestTrampolineTemplate - __start_HostToGuestTrampolineTemplate; - const auto InstanceInfoOffset = Length - sizeof(TrampolineInstanceInfo); - - if (ThunkHandler->HostTrampolineInstanceDataAvailable < Length) { - 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"); - } - - const TrampolineInstanceInfo NewTrampolineInfo { - .HostPacker = reinterpret_cast(HostPackerEntry->second), - .CallCallback = (uintptr_t)&CallCallback, - .GuestUnpacker = args->GuestUnpacker, - .GuestTarget = args->GuestTarget - }; - - uint8_t* const HostTrampoline = ThunkHandler->HostTrampolineInstanceDataPtr; - ThunkHandler->HostTrampolineInstanceDataAvailable -= Length; - ThunkHandler->HostTrampolineInstanceDataPtr += Length; - - memcpy(HostTrampoline, (void*)&HostToGuestTrampolineTemplate, Length); - memcpy(HostTrampoline + InstanceInfoOffset, &NewTrampolineInfo, sizeof(NewTrampolineInfo)); - - args->rv = reinterpret_cast(HostTrampoline); - - ThunkHandler->GuestcallToHostTrampoline[gci] = args->rv; + args->rv = (uintptr_t)MakeHostTrampolineForGuestFunction(nullptr, args->GuestTarget, args->GuestUnpacker); } /** @@ -374,8 +313,6 @@ namespace FEXCore { } } - public: - ThunkedFunction* LookupThunk(const IR::SHA256Sum &sha256) { std::shared_lock lk(ThunksMutex); @@ -397,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 e49a3397f..a887ea69d 100644 --- a/ThunkLibs/Generator/gen.cpp +++ b/ThunkLibs/Generator/gen.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include @@ -18,9 +19,6 @@ struct FunctionParams { struct ThunkedCallback : FunctionParams { clang::QualType return_type; - std::size_t callback_index; - std::size_t user_arg_index; - bool is_stub = false; // Callback will be replaced by a stub that calls std::abort bool is_guest = false; // Callback will never be called on the host bool is_variadic = false; @@ -47,11 +45,6 @@ struct ThunkedFunction : FunctionParams { // This is implied e.g. for thunks generated for variadic functions bool custom_host_impl = false; - // If true, the unpacking function will use an extra argument for a - // host function pointer that is called instead of calling the host library - // function directly. - bool is_hostcall = false; - std::string GetOriginalFunctionName() const { const std::string suffix = "_internal"; assert(function_name.length() > suffix.size()); @@ -93,6 +86,7 @@ struct ThunkedAPIFunction : FunctionParams { static std::vector thunks; static std::vector thunked_api; +static std::unordered_set funcptr_types; static std::optional lib_version; struct NamespaceInfo { @@ -263,6 +257,20 @@ public: * Matches "template<> struct fex_gen_config { ... }" */ bool VisitClassTemplateSpecializationDecl(clang::ClassTemplateSpecializationDecl* decl) try { + if (decl->getName() == "fex_gen_type") { + const auto& template_args = decl->getTemplateArgs(); + assert(template_args.size() == 1); + + // NOTE: Function types that are equivalent but use differently + // named types (e.g. GLuint/GLenum) are represented by + // different Type instances. The canonical type they refer + // to is unique, however. + auto type = context.getCanonicalType(template_args[0].getAsType()).getTypePtr(); + funcptr_types.insert(type); + + return true; + } + if (decl->getName() != "fex_gen_config") { return true; } @@ -323,8 +331,13 @@ public: } data.callbacks.emplace(param_idx, callback); - // TODO: Support for more than one callback is untested - assert(data.callbacks.size() == 1); + if (!callback.is_stub && !callback.is_guest) { + funcptr_types.insert(context.getCanonicalType(funcptr)); + } + + if (data.callbacks.size() != 1) { + throw Error(decl->getBeginLoc(), "Support for more than one callback is untested"); + } if (funcptr->isVariadic() && !callback.is_stub) { throw Error(decl->getBeginLoc(), "Variadic callbacks are not supported"); } @@ -360,13 +373,9 @@ public: data.custom_host_impl = true; } - // For indirect calls, register a second thunk with the callee pointer as an extra argument + // For indirect calls, register the function signature as a function pointer type if (namespace_info.indirect_guest_calls) { - auto hostcall_data = data; - hostcall_data.function_name = "hostcall_" + data.function_name; - hostcall_data.param_types.push_back(context.getUIntPtrType()); - hostcall_data.is_hostcall = true; - thunks.push_back(std::move(hostcall_data)); + funcptr_types.insert(context.getCanonicalType(emitted_function->getFunctionType())); } thunks.push_back(std::move(data)); @@ -395,6 +404,7 @@ GenerateThunkLibsAction::GenerateThunkLibsAction(const std::string& libname_, co thunks.clear(); thunked_api.clear(); + funcptr_types.clear(); namespaces.clear(); lib_version = std::nullopt; } @@ -410,12 +420,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(); @@ -461,8 +467,8 @@ void GenerateThunkLibsAction::EndSourceFileAction() { return sha256; }; - auto get_callback_name = [](std::string_view function_name, unsigned param_index, bool is_first_cb) -> std::string { - return std::string { function_name } + "CBFN" + (is_first_cb ? "" : std::to_string(param_index)); + auto get_callback_name = [](std::string_view function_name, unsigned param_index) -> std::string { + return std::string { function_name } + "CBFN" + std::to_string(param_index); }; if (!output_filenames.thunks.empty()) { @@ -479,25 +485,27 @@ 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"; + + for (auto type_it = funcptr_types.begin(); type_it != funcptr_types.end(); ++type_it) { + auto* type = *type_it; + std::string funcptr_signature = clang::QualType { type, 0 }.getAsString(); + + auto cb_sha256 = get_sha256("fexcallback_" + funcptr_signature); + std::stringstream cb_sha256_ss; + for (auto c : cb_sha256) { + cb_sha256_ss << "0x" << std::hex << std::setw(2) << std::setfill('0') << +c << ", "; + } + auto cb_sha256_str = std::move(cb_sha256_ss).str(); + cb_sha256_str.pop_back(); + 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"; + } } if (!output_filenames.function_packs_public.empty()) { @@ -565,6 +573,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); @@ -573,9 +582,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"; @@ -612,37 +619,14 @@ void GenerateThunkLibsAction::EndSourceFileAction() { /* Generate stub callbacks */ for (auto& [cb_idx, cb] : thunk.callbacks) { if (cb.is_stub) { - bool is_first_cb = (cb_idx == thunk.callbacks.begin()->first); const char* variadic_ellipsis = cb.is_variadic ? ", ..." : ""; - auto cb_function_name = "fexfn_unpack_" + get_callback_name(function_name, cb_idx, is_first_cb) + "_stub"; + auto cb_function_name = "fexfn_unpack_" + get_callback_name(function_name, cb_idx) + "_stub"; file << "[[noreturn]] static " << cb.return_type.getAsString() << " " << cb_function_name << "(" << format_function_params(cb) << variadic_ellipsis << ") {\n"; 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"; } } @@ -650,11 +634,7 @@ void GenerateThunkLibsAction::EndSourceFileAction() { FunctionParams args = thunk; auto function_to_call = "fexldr_ptr_" + libname + "_" + function_name; - if (thunk.is_hostcall) { - // Get the host function pointer by casting the last parameter to the correct signature - args.param_types.pop_back(); - function_to_call = "reinterpret_cast<" + thunk.return_type.getAsString() + "(*)(" + format_function_params(args) + ")>(args->a_" + std::to_string(args.param_types.size()) + ")"; - } else if (thunk.custom_host_impl) { + if (thunk.custom_host_impl) { function_to_call = "fexfn_impl_" + libname + "_" + function_name; } @@ -664,10 +644,14 @@ void GenerateThunkLibsAction::EndSourceFileAction() { auto format_param = [&](std::size_t idx) { auto cb = thunk.callbacks.find(idx); if (cb != thunk.callbacks.end() && cb->second.is_stub) { - bool is_first_cb = (cb->first == thunk.callbacks.begin()->first); - return "fexfn_unpack_" + get_callback_name(function_name, cb->first, is_first_cb) + "_stub"; + return "fexfn_unpack_" + get_callback_name(function_name, cb->first) + "_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); } @@ -694,20 +678,19 @@ 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); + for (auto& type : funcptr_types) { + std::string mangled_name = clang::QualType { type, 0 }.getAsString(); + { + auto cb_sha256 = get_sha256("fexcallback_" + mangled_name); - auto cb_sha256 = get_sha256(cb_function_name); - - file << "{(uint8_t*)\""; + std::stringstream cb_sha256_ss; for (auto c : cb_sha256) { - file << "\\x" << std::hex << std::setw(2) << std::setfill('0') << +c; + cb_sha256_ss << "\\x" << std::hex << std::setw(2) << std::setfill('0') << +c; } - file << "\", (void(*)(void *))&fexfn_pack_guestcall_" << cb_function_name << "}, // " << libname << ":" << cb_function_name << "\n"; + auto cb_sha256_str = std::move(cb_sha256_ss).str(); + file << " {(uint8_t*)\"" << cb_sha256_str << "\", (void(*)(void *))&CallbackUnpack<" << mangled_name << ">::ForIndirectCall},\n"; } } } @@ -744,34 +727,6 @@ void GenerateThunkLibsAction::EndSourceFileAction() { } } - if (!output_filenames.callback_unpacks.empty()) { - 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()) { std::ofstream file(output_filenames.symbol_list); diff --git a/ThunkLibs/Generator/interface.h b/ThunkLibs/Generator/interface.h index 67eeb3077..87092f8f1 100644 --- a/ThunkLibs/Generator/interface.h +++ b/ThunkLibs/Generator/interface.h @@ -17,8 +17,6 @@ struct OutputFilenames { std::string function_packs_public; // Guest + Host - std::string callback_unpacks; - std::string symbol_list; }; diff --git a/ThunkLibs/Generator/main.cpp b/ThunkLibs/Generator/main.cpp index 69e4e8bf9..184e57dc6 100644 --- a/ThunkLibs/Generator/main.cpp +++ b/ThunkLibs/Generator/main.cpp @@ -51,8 +51,6 @@ int main(int argc, char* argv[]) { output_filenames.function_packs = out_filename; } else if (target == "-function_packs_public") { output_filenames.function_packs_public = out_filename; - } else if (target == "-callback_unpacks") { - output_filenames.callback_unpacks = out_filename; } else if (target == "-symbol_list") { output_filenames.symbol_list = out_filename; } else { diff --git a/ThunkLibs/GuestLibs/CMakeLists.txt b/ThunkLibs/GuestLibs/CMakeLists.txt index 77d65100d..72d23c070 100644 --- a/ThunkLibs/GuestLibs/CMakeLists.txt +++ b/ThunkLibs/GuestLibs/CMakeLists.txt @@ -40,7 +40,7 @@ function(generate NAME SOURCE_FILE) OUTPUT "${OUTFILE}" DEPENDS "${GENERATOR_EXE}" DEPENDS "${SOURCE_FILE}" - COMMAND "${GENERATOR_EXE}" "${SOURCE_FILE}" "${NAME}" "-${WHAT}" "${OUTFILE}" -- -std=c++17 + COMMAND "${GENERATOR_EXE}" "${SOURCE_FILE}" "${NAME}" "-${WHAT}" "${OUTFILE}" -- -std=c++17 -DGUEST_THUNK_LIBRARY # Expand include directories to space-separated list of -isystem parameters "$<$:;-isystem$>" VERBATIM @@ -103,10 +103,10 @@ target_link_libraries(GL-guest PRIVATE X11) # target_link_libraries(SDL2-guest PRIVATE GL) # target_link_libraries(SDL2-guest PRIVATE dl) -generate(libX11 ${CMAKE_CURRENT_SOURCE_DIR}/../libX11/libX11_interface.cpp thunks function_packs function_packs_public callback_unpacks) +generate(libX11 ${CMAKE_CURRENT_SOURCE_DIR}/../libX11/libX11_interface.cpp thunks function_packs function_packs_public) add_guest_lib(X11) -generate(libXext ${CMAKE_CURRENT_SOURCE_DIR}/../libXext/libXext_interface.cpp thunks function_packs function_packs_public callback_unpacks) +generate(libXext ${CMAKE_CURRENT_SOURCE_DIR}/../libXext/libXext_interface.cpp thunks function_packs function_packs_public) add_guest_lib(Xext) generate(libXrender ${CMAKE_CURRENT_SOURCE_DIR}/../libXrender/libXrender_interface.cpp thunks function_packs function_packs_public) @@ -119,7 +119,7 @@ generate(libvulkan ${CMAKE_CURRENT_SOURCE_DIR}/../libvulkan/libvulkan_interface. target_include_directories(libvulkan-guest-deps INTERFACE ${FEX_PROJECT_SOURCE_DIR}/External/Vulkan-Headers/include/) add_guest_lib(vulkan) -generate(libxcb ${CMAKE_CURRENT_SOURCE_DIR}/../libxcb/libxcb_interface.cpp thunks function_packs function_packs_public callback_unpacks) +generate(libxcb ${CMAKE_CURRENT_SOURCE_DIR}/../libxcb/libxcb_interface.cpp thunks function_packs function_packs_public) add_guest_lib(xcb) generate(libxcb-dri2 ${CMAKE_CURRENT_SOURCE_DIR}/../libxcb-dri2/libxcb-dri2_interface.cpp thunks function_packs function_packs_public) 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/README.md b/ThunkLibs/README.md index 09c20c9f9..32cb8d1b1 100644 --- a/ThunkLibs/README.md +++ b/ThunkLibs/README.md @@ -65,7 +65,6 @@ For each thunked library, the generator outputs the following files: - `ldr.inl`: Host loader that dlopens/dlsyms the "real" host library for the implementation functions. - `ldr_ptrs.inl`: Host loader pointer declarations, used by ldr and function_unpacks - `tab_function_unpacks.inl`: Host function unpackers list, passed to FEX after Host library init so it can resolve the Guest Thunks to Host functions -- `callback_unpacks.inl`: Guest callback unpackers ## Adding a new library diff --git a/ThunkLibs/include/common/Guest.h b/ThunkLibs/include/common/Guest.h index 69b736d84..e0414761a 100644 --- a/ThunkLibs/include/common/Guest.h +++ b/ThunkLibs/include/common/Guest.h @@ -2,10 +2,21 @@ #include #include +#include "PackedArguments.h" + +template +const int (*fexthunks_invoke_callback)(void*); + #ifndef _M_ARM_64 #define MAKE_THUNK(lib, name, hash) \ extern "C" int fexthunks_##lib##_##name(void *args); \ asm(".text\nfexthunks_" #lib "_" #name ":\n.byte 0xF, 0x3F\n.byte " hash ); + +#define MAKE_CALLBACK_THUNK(name, signature, hash) \ + extern "C" int fexthunks_##name(void *args); \ + asm(".text\nfexthunks_" #name ":\n.byte 0xF, 0x3F\n.byte " hash ); \ + template<> inline constexpr int (*fexthunks_invoke_callback)(void*) = fexthunks_##name; + #else // We're compiling for IDE integration, so provide a dummy-implementation that just calls an undefined function. // The name of that function serves as an error message if this library somehow gets loaded at runtime. @@ -15,6 +26,9 @@ extern "C" void BROKEN_INSTALL___TRIED_LOADING_AARCH64_BUILD_OF_GUEST_THUNK(); BROKEN_INSTALL___TRIED_LOADING_AARCH64_BUILD_OF_GUEST_THUNK(); \ return 0; \ } +#define MAKE_CALLBACK_THUNK(name, signature, hash) \ + extern "C" int fexthunks_##name(void *args); \ + template<> inline constexpr int (*fexthunks_invoke_callback)(void*) = fexthunks_##name; #endif // Generated fexfn_pack_ symbols should be hidden by default, but clang does @@ -35,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() \ @@ -68,94 +82,13 @@ inline bool IsLibLoaded(const char *libname) { return argsrv.rv; } -template -struct PackedArguments; - -template -struct PackedArguments { A0 a0; R rv; }; -template -struct PackedArguments { A0 a0; A1 a1; R rv; }; -template -struct PackedArguments { A0 a0; A1 a1; A2 a2; R rv; }; -template -struct PackedArguments { A0 a0; A1 a1; A2 a2; A3 a3; R rv; }; -template -struct PackedArguments { A0 a0; A1 a1; A2 a2; A3 a3; A4 a4; R rv; }; -template -struct PackedArguments { A0 a0; A1 a1; A2 a2; A3 a3; A4 a4; A5 a5; R rv; }; -template -struct PackedArguments { A0 a0; A1 a1; A2 a2; A3 a3; A4 a4; A5 a5; A6 a6; R rv; }; -template -struct PackedArguments { A0 a0; A1 a1; A2 a2; A3 a3; A4 a4; A5 a5; A6 a6; A7 a7; R rv; }; -template -struct PackedArguments { A0 a0; A1 a1; A2 a2; A3 a3; A4 a4; A5 a5; A6 a6; A7 a7; A8 a8; R rv; }; -template -struct PackedArguments { A0 a0; A1 a1; A2 a2; A3 a3; A4 a4; A5 a5; A6 a6; A7 a7; A8 a8; A9 a9; R rv; }; -template -struct PackedArguments { A0 a0; A1 a1; A2 a2; A3 a3; A4 a4; A5 a5; A6 a6; A7 a7; A8 a8; A9 a9; A10 a10; R rv; }; -template -struct PackedArguments { A0 a0; A1 a1; A2 a2; A3 a3; A4 a4; A5 a5; A6 a6; A7 a7; A8 a8; A9 a9; A10 a10; A11 a11; R rv; }; -template -struct PackedArguments { A0 a0; A1 a1; A2 a2; A3 a3; A4 a4; A5 a5; A6 a6; A7 a7; A8 a8; A9 a9; A10 a10; A11 a11; A12 a12; R rv; }; - -template -struct PackedArguments { A0 a0; A1 a1; A2 a2; A3 a3; A4 a4; A5 a5; A6 a6; A7 a7; A8 a8; A9 a9; A10 a10; A11 a11; A12 a12; A13 a13; R rv; }; - -template -struct PackedArguments { - A0 a0; A1 a1; A2 a2; A3 a3; A4 a4; A5 a5; A6 a6; A7 a7; A8 a8; A9 a9; - A10 a10; A11 a11; A12 a12; A13 a13; A14 a14; A15 a15; A16 a16; A17 a17; A18 a18; A19 a19; - A20 a20; A21 a21; A22 a22; A23 a23; R rv; -}; - -template -struct PackedArguments { A0 a0; }; -template -struct PackedArguments { A0 a0; A1 a1; }; -template -struct PackedArguments { A0 a0; A1 a1; A2 a2; }; -template -struct PackedArguments { A0 a0; A1 a1; A2 a2; A3 a3; }; -template -struct PackedArguments { A0 a0; A1 a1; A2 a2; A3 a3; A4 a4; }; -template -struct PackedArguments { A0 a0; A1 a1; A2 a2; A3 a3; A4 a4; A5 a5; }; -template -struct PackedArguments { A0 a0; A1 a1; A2 a2; A3 a3; A4 a4; A5 a5; A6 a6; }; -template -struct PackedArguments { A0 a0; A1 a1; A2 a2; A3 a3; A4 a4; A5 a5; A6 a6; A7 a7; }; -template -struct PackedArguments { A0 a0; A1 a1; A2 a2; A3 a3; A4 a4; A5 a5; A6 a6; A7 a7; A8 a8; }; -template -struct PackedArguments { A0 a0; A1 a1; A2 a2; A3 a3; A4 a4; A5 a5; A6 a6; A7 a7; A8 a8; A9 a9; }; -template -struct PackedArguments { A0 a0; A1 a1; A2 a2; A3 a3; A4 a4; A5 a5; A6 a6; A7 a7; A8 a8; A9 a9; A10 a10; }; -template -struct PackedArguments { A0 a0; A1 a1; A2 a2; A3 a3; A4 a4; A5 a5; A6 a6; A7 a7; A8 a8; A9 a9; A10 a10; A11 a11; }; -template -struct PackedArguments { A0 a0; A1 a1; A2 a2; A3 a3; A4 a4; A5 a5; A6 a6; A7 a7; A8 a8; A9 a9; A10 a10; A11 a11; A12 a12; }; -template -struct PackedArguments { A0 a0; A1 a1; A2 a2; A3 a3; A4 a4; A5 a5; A6 a6; A7 a7; A8 a8; A9 a9; A10 a10; A11 a11; A12 a12; A13 a13; }; -template -struct PackedArguments { A0 a0; A1 a1; A2 a2; A3 a3; A4 a4; A5 a5; A6 a6; A7 a7; A8 a8; A9 a9; A10 a10; A11 a11; A12 a12; A13 a13; A14 a14; }; -template -struct PackedArguments { A0 a0; A1 a1; A2 a2; A3 a3; A4 a4; A5 a5; A6 a6; A7 a7; A8 a8; A9 a9; A10 a10; A11 a11; A12 a12; A13 a13; A14 a14; A15 a15; }; -template -struct PackedArguments { A0 a0; A1 a1; A2 a2; A3 a3; A4 a4; A5 a5; A6 a6; A7 a7; A8 a8; A9 a9; A10 a10; A11 a11; A12 a12; A13 a13; A14 a14; A15 a15; A16 a16; }; -template -struct PackedArguments { A0 a0; A1 a1; A2 a2; A3 a3; A4 a4; A5 a5; A6 a6; A7 a7; A8 a8; A9 a9; A10 a10; A11 a11; A12 a12; A13 a13; A14 a14; A15 a15; A16 a16; A17 a17; }; - // Helper template that packs the given arguments and invokes a thunk at the // address stored in the `r11` guest register. The signature of the thunk must // be specified at compile-time via the Thunk template parameter. // Other than reading the thunk address from `r11`, this is equivalent to the // fexfn_pack_* functions generated for global API functions. template -inline Result CallHostThunkFromRuntimePointer(Args... args) { +inline Result CallHostFunction(Args... args) { #ifndef _M_ARM_64 uintptr_t host_addr; asm volatile("mov %%r11, %0" : "=r" (host_addr)); @@ -176,33 +109,61 @@ inline Result CallHostThunkFromRuntimePointer(Args... args) { } } -// Convenience wrapper that returns the function pointer to a -// CallHostThunkFromRuntimePointer instantiation matching the function -// signature of `host_func` -template -static auto GetCallerForHostThunkFromRuntimePointer(Result (*host_func)(Args...)) +// Convenience wrapper that returns the function pointer to a CallHostFunction +// instantiation matching the function signature of `host_func` +template +static auto GetCallerForHostFunction(Result (*host_func)(Args...)) -> Result(*)(Args...) { - return CallHostThunkFromRuntimePointer; + return &CallHostFunction, Result, Args...>; +} + +// Ensures the given host function can safely be called from guest code. +template +inline void MakeHostFunctionGuestCallable(Result (*host_func)(Args...)) { + auto caller = (uintptr_t)GetCallerForHostFunction(host_func); + LinkAddressToFunction((uintptr_t)host_func, (uintptr_t)caller); } 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 af3acd82c..d9b24f655 100644 --- a/ThunkLibs/include/common/Host.h +++ b/ThunkLibs/include/common/Host.h @@ -5,7 +5,34 @@ $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; @@ -45,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; \ } \ @@ -75,4 +102,89 @@ 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 +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); + uintptr_t cb; + static_assert(CBIndex <= 17 || CBIndex == 23); + if constexpr(CBIndex == 0) { + cb = args->a0; + } else if constexpr(CBIndex == 1) { + cb = args->a1; + } else if constexpr(CBIndex == 2) { + cb = args->a2; + } else if constexpr(CBIndex == 3) { + cb = args->a3; + } else if constexpr(CBIndex == 4) { + cb = args->a4; + } else if constexpr(CBIndex == 5) { + cb = args->a5; + } else if constexpr(CBIndex == 6) { + cb = args->a6; + } else if constexpr(CBIndex == 7) { + cb = args->a7; + } else if constexpr(CBIndex == 8) { + cb = args->a8; + } else if constexpr(CBIndex == 9) { + cb = args->a9; + } else if constexpr(CBIndex == 10) { + cb = args->a10; + } else if constexpr(CBIndex == 11) { + cb = args->a11; + } else if constexpr(CBIndex == 12) { + cb = args->a12; + } else if constexpr(CBIndex == 13) { + cb = args->a13; + } else if constexpr(CBIndex == 14) { + cb = args->a14; + } else if constexpr(CBIndex == 15) { + cb = args->a15; + } else if constexpr(CBIndex == 16) { + cb = args->a16; + } else if constexpr(CBIndex == 17) { + cb = args->a17; + } else if constexpr(CBIndex == 23) { + cb = args->a23; + } + auto callback = reinterpret_cast(cb); + Invoke(callback, *args); + } +}; + +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/ThunkLibs/include/common/PackedArguments.h b/ThunkLibs/include/common/PackedArguments.h new file mode 100644 index 000000000..2578e545e --- /dev/null +++ b/ThunkLibs/include/common/PackedArguments.h @@ -0,0 +1,191 @@ +#pragma once + +#include +#include + +template +struct PackedArguments; + +template +struct PackedArguments { R rv; }; +template +struct PackedArguments { A0 a0; R rv; }; +template +struct PackedArguments { A0 a0; A1 a1; R rv; }; +template +struct PackedArguments { A0 a0; A1 a1; A2 a2; R rv; }; +template +struct PackedArguments { A0 a0; A1 a1; A2 a2; A3 a3; R rv; }; +template +struct PackedArguments { A0 a0; A1 a1; A2 a2; A3 a3; A4 a4; R rv; }; +template +struct PackedArguments { A0 a0; A1 a1; A2 a2; A3 a3; A4 a4; A5 a5; R rv; }; +template +struct PackedArguments { A0 a0; A1 a1; A2 a2; A3 a3; A4 a4; A5 a5; A6 a6; R rv; }; +template +struct PackedArguments { A0 a0; A1 a1; A2 a2; A3 a3; A4 a4; A5 a5; A6 a6; A7 a7; R rv; }; +template +struct PackedArguments { A0 a0; A1 a1; A2 a2; A3 a3; A4 a4; A5 a5; A6 a6; A7 a7; A8 a8; R rv; }; +template +struct PackedArguments { A0 a0; A1 a1; A2 a2; A3 a3; A4 a4; A5 a5; A6 a6; A7 a7; A8 a8; A9 a9; R rv; }; +template +struct PackedArguments { A0 a0; A1 a1; A2 a2; A3 a3; A4 a4; A5 a5; A6 a6; A7 a7; A8 a8; A9 a9; A10 a10; R rv; }; +template +struct PackedArguments { A0 a0; A1 a1; A2 a2; A3 a3; A4 a4; A5 a5; A6 a6; A7 a7; A8 a8; A9 a9; A10 a10; A11 a11; R rv; }; +template +struct PackedArguments { A0 a0; A1 a1; A2 a2; A3 a3; A4 a4; A5 a5; A6 a6; A7 a7; A8 a8; A9 a9; A10 a10; A11 a11; A12 a12; R rv; }; + +template +struct PackedArguments { A0 a0; A1 a1; A2 a2; A3 a3; A4 a4; A5 a5; A6 a6; A7 a7; A8 a8; A9 a9; A10 a10; A11 a11; A12 a12; A13 a13; R rv; }; + +template +struct PackedArguments { + A0 a0; A1 a1; A2 a2; A3 a3; A4 a4; A5 a5; A6 a6; A7 a7; A8 a8; A9 a9; + A10 a10; A11 a11; A12 a12; A13 a13; A14 a14; A15 a15; A16 a16; A17 a17; A18 a18; A19 a19; + A20 a20; A21 a21; A22 a22; R rv; +}; + +template +struct PackedArguments { + A0 a0; A1 a1; A2 a2; A3 a3; A4 a4; A5 a5; A6 a6; A7 a7; A8 a8; A9 a9; + A10 a10; A11 a11; A12 a12; A13 a13; A14 a14; A15 a15; A16 a16; A17 a17; A18 a18; A19 a19; + A20 a20; A21 a21; A22 a22; A23 a23; R rv; +}; + +template<> +struct PackedArguments { }; +template +struct PackedArguments { A0 a0; }; +template +struct PackedArguments { A0 a0; A1 a1; }; +template +struct PackedArguments { A0 a0; A1 a1; A2 a2; }; +template +struct PackedArguments { A0 a0; A1 a1; A2 a2; A3 a3; }; +template +struct PackedArguments { A0 a0; A1 a1; A2 a2; A3 a3; A4 a4; }; +template +struct PackedArguments { A0 a0; A1 a1; A2 a2; A3 a3; A4 a4; A5 a5; }; +template +struct PackedArguments { A0 a0; A1 a1; A2 a2; A3 a3; A4 a4; A5 a5; A6 a6; }; +template +struct PackedArguments { A0 a0; A1 a1; A2 a2; A3 a3; A4 a4; A5 a5; A6 a6; A7 a7; }; +template +struct PackedArguments { A0 a0; A1 a1; A2 a2; A3 a3; A4 a4; A5 a5; A6 a6; A7 a7; A8 a8; }; +template +struct PackedArguments { A0 a0; A1 a1; A2 a2; A3 a3; A4 a4; A5 a5; A6 a6; A7 a7; A8 a8; A9 a9; }; +template +struct PackedArguments { A0 a0; A1 a1; A2 a2; A3 a3; A4 a4; A5 a5; A6 a6; A7 a7; A8 a8; A9 a9; A10 a10; }; +template +struct PackedArguments { A0 a0; A1 a1; A2 a2; A3 a3; A4 a4; A5 a5; A6 a6; A7 a7; A8 a8; A9 a9; A10 a10; A11 a11; }; +template +struct PackedArguments { A0 a0; A1 a1; A2 a2; A3 a3; A4 a4; A5 a5; A6 a6; A7 a7; A8 a8; A9 a9; A10 a10; A11 a11; A12 a12; }; +template +struct PackedArguments { A0 a0; A1 a1; A2 a2; A3 a3; A4 a4; A5 a5; A6 a6; A7 a7; A8 a8; A9 a9; A10 a10; A11 a11; A12 a12; A13 a13; }; +template +struct PackedArguments { A0 a0; A1 a1; A2 a2; A3 a3; A4 a4; A5 a5; A6 a6; A7 a7; A8 a8; A9 a9; A10 a10; A11 a11; A12 a12; A13 a13; A14 a14; }; +template +struct PackedArguments { A0 a0; A1 a1; A2 a2; A3 a3; A4 a4; A5 a5; A6 a6; A7 a7; A8 a8; A9 a9; A10 a10; A11 a11; A12 a12; A13 a13; A14 a14; A15 a15; }; +template +struct PackedArguments { A0 a0; A1 a1; A2 a2; A3 a3; A4 a4; A5 a5; A6 a6; A7 a7; A8 a8; A9 a9; A10 a10; A11 a11; A12 a12; A13 a13; A14 a14; A15 a15; A16 a16; }; +template +struct PackedArguments { A0 a0; A1 a1; A2 a2; A3 a3; A4 a4; A5 a5; A6 a6; A7 a7; A8 a8; A9 a9; A10 a10; A11 a11; A12 a12; A13 a13; A14 a14; A15 a15; A16 a16; A17 a17; }; + +template +void Invoke(Result(*func)(Args...), PackedArguments& args) { + constexpr auto NumArgs = sizeof...(Args); + static_assert(NumArgs <= 18 || NumArgs == 24); + if constexpr (std::is_void_v) { + if constexpr (NumArgs == 0) { + func(); + } else if constexpr (NumArgs == 1) { + func(args.a0); + } else if constexpr (NumArgs == 2) { + func(args.a0, args.a1); + } else if constexpr (NumArgs == 3) { + func(args.a0, args.a1, args.a2); + } else if constexpr (NumArgs == 4) { + func(args.a0, args.a1, args.a2, args.a3); + } else if constexpr (NumArgs == 5) { + func(args.a0, args.a1, args.a2, args.a3, args.a4); + } else if constexpr (NumArgs == 6) { + func(args.a0, args.a1, args.a2, args.a3, args.a4, args.a5); + } else if constexpr (NumArgs == 7) { + func(args.a0, args.a1, args.a2, args.a3, args.a4, args.a5, args.a6); + } else if constexpr (NumArgs == 8) { + func(args.a0, args.a1, args.a2, args.a3, args.a4, args.a5, args.a6, args.a7); + } else if constexpr (NumArgs == 9) { + func(args.a0, args.a1, args.a2, args.a3, args.a4, args.a5, args.a6, args.a7, args.a8); + } else if constexpr (NumArgs == 10) { + func(args.a0, args.a1, args.a2, args.a3, args.a4, args.a5, args.a6, args.a7, args.a8, args.a9); + } else if constexpr (NumArgs == 11) { + func(args.a0, args.a1, args.a2, args.a3, args.a4, args.a5, args.a6, args.a7, args.a8, args.a9, args.a10); + } else if constexpr (NumArgs == 12) { + func(args.a0, args.a1, args.a2, args.a3, args.a4, args.a5, args.a6, args.a7, args.a8, args.a9, args.a10, args.a11); + } else if constexpr (NumArgs == 13) { + func(args.a0, args.a1, args.a2, args.a3, args.a4, args.a5, args.a6, args.a7, args.a8, args.a9, args.a10, args.a11, args.a12); + } else if constexpr (NumArgs == 14) { + func(args.a0, args.a1, args.a2, args.a3, args.a4, args.a5, args.a6, args.a7, args.a8, args.a9, args.a10, args.a11, args.a12, args.a13); + } else if constexpr (NumArgs == 15) { + func(args.a0, args.a1, args.a2, args.a3, args.a4, args.a5, args.a6, args.a7, args.a8, args.a9, args.a10, args.a11, args.a12, args.a13, args.a14); + } else if constexpr (NumArgs == 16) { + func(args.a0, args.a1, args.a2, args.a3, args.a4, args.a5, args.a6, args.a7, args.a8, args.a9, args.a10, args.a11, args.a12, args.a13, args.a14, args.a15); + } else if constexpr (NumArgs == 17) { + func(args.a0, args.a1, args.a2, args.a3, args.a4, args.a5, args.a6, args.a7, args.a8, args.a9, args.a10, args.a11, args.a12, args.a13, args.a14, args.a15, args.a16); + } else if constexpr (NumArgs == 18) { + func(args.a0, args.a1, args.a2, args.a3, args.a4, args.a5, args.a6, args.a7, args.a8, args.a9, args.a10, args.a11, args.a12, args.a13, args.a14, args.a15, args.a16, args.a17); + } else if constexpr (NumArgs == 24) { + func(args.a0, args.a1, args.a2, args.a3, args.a4, args.a5, args.a6, args.a7, args.a8, args.a9, args.a10, args.a11, args.a12, args.a13, args.a14, args.a15, args.a16, args.a17, args.a18, args.a19, args.a20, args.a21, args.a22, args.a23); + } + } else { + if constexpr (NumArgs == 0) { + args.rv = func(); + } else if constexpr (NumArgs == 1) { + args.rv = func(args.a0); + } else if constexpr (NumArgs == 2) { + args.rv = func(args.a0, args.a1); + } else if constexpr (NumArgs == 3) { + args.rv = func(args.a0, args.a1, args.a2); + } else if constexpr (NumArgs == 4) { + args.rv = func(args.a0, args.a1, args.a2, args.a3); + } else if constexpr (NumArgs == 5) { + args.rv = func(args.a0, args.a1, args.a2, args.a3, args.a4); + } else if constexpr (NumArgs == 6) { + args.rv = func(args.a0, args.a1, args.a2, args.a3, args.a4, args.a5); + } else if constexpr (NumArgs == 7) { + args.rv = func(args.a0, args.a1, args.a2, args.a3, args.a4, args.a5, args.a6); + } else if constexpr (NumArgs == 8) { + args.rv = func(args.a0, args.a1, args.a2, args.a3, args.a4, args.a5, args.a6, args.a7); + } else if constexpr (NumArgs == 9) { + args.rv = func(args.a0, args.a1, args.a2, args.a3, args.a4, args.a5, args.a6, args.a7, args.a8); + } else if constexpr (NumArgs == 10) { + args.rv = func(args.a0, args.a1, args.a2, args.a3, args.a4, args.a5, args.a6, args.a7, args.a8, args.a9); + } else if constexpr (NumArgs == 11) { + args.rv = func(args.a0, args.a1, args.a2, args.a3, args.a4, args.a5, args.a6, args.a7, args.a8, args.a9, args.a10); + } else if constexpr (NumArgs == 12) { + args.rv = func(args.a0, args.a1, args.a2, args.a3, args.a4, args.a5, args.a6, args.a7, args.a8, args.a9, args.a10, args.a11); + } else if constexpr (NumArgs == 13) { + args.rv = func(args.a0, args.a1, args.a2, args.a3, args.a4, args.a5, args.a6, args.a7, args.a8, args.a9, args.a10, args.a11, args.a12); + } else if constexpr (NumArgs == 14) { + args.rv = func(args.a0, args.a1, args.a2, args.a3, args.a4, args.a5, args.a6, args.a7, args.a8, args.a9, args.a10, args.a11, args.a12, args.a13); + } else if constexpr (NumArgs == 15) { + args.rv = func(args.a0, args.a1, args.a2, args.a3, args.a4, args.a5, args.a6, args.a7, args.a8, args.a9, args.a10, args.a11, args.a12, args.a13, args.a14); + } else if constexpr (NumArgs == 16) { + args.rv = func(args.a0, args.a1, args.a2, args.a3, args.a4, args.a5, args.a6, args.a7, args.a8, args.a9, args.a10, args.a11, args.a12, args.a13, args.a14, args.a15); + } else if constexpr (NumArgs == 17) { + args.rv = func(args.a0, args.a1, args.a2, args.a3, args.a4, args.a5, args.a6, args.a7, args.a8, args.a9, args.a10, args.a11, args.a12, args.a13, args.a14, args.a15, args.a16); + } else if constexpr (NumArgs == 18) { + args.rv = func(args.a0, args.a1, args.a2, args.a3, args.a4, args.a5, args.a6, args.a7, args.a8, args.a9, args.a10, args.a11, args.a12, args.a13, args.a14, args.a15, args.a16, args.a17); + } else if constexpr (NumArgs == 24) { + args.rv = func(args.a0, args.a1, args.a2, args.a3, args.a4, args.a5, args.a6, args.a7, args.a8, args.a9, args.a10, args.a11, args.a12, args.a13, args.a14, args.a15, args.a16, args.a17, args.a18, args.a19, args.a20, args.a21, args.a22, args.a23); + } + } +} diff --git a/ThunkLibs/libGL/libGL_Guest.cpp b/ThunkLibs/libGL/libGL_Guest.cpp index 01024b5ee..65635cb8c 100644 --- a/ThunkLibs/libGL/libGL_Guest.cpp +++ b/ThunkLibs/libGL/libGL_Guest.cpp @@ -35,9 +35,9 @@ typedef void voidFunc(); // linked to the corresponding host function pointer const std::unordered_map HostPtrInvokers = std::invoke([]() { -#define PAIR(name, unused) Ret[#name] = reinterpret_cast(GetCallerForHostThunkFromRuntimePointer(name)); +#define PAIR(name, unused) Ret[#name] = reinterpret_cast(GetCallerForHostFunction(name)); std::unordered_map Ret; - FOREACH_internal_SYMBOL(PAIR) + FOREACH_internal_SYMBOL(PAIR); return Ret; #undef PAIR }); diff --git a/ThunkLibs/libX11/libX11_Guest.cpp b/ThunkLibs/libX11/libX11_Guest.cpp index 0df8e060e..4818297e5 100644 --- a/ThunkLibs/libX11/libX11_Guest.cpp +++ b/ThunkLibs/libX11/libX11_Guest.cpp @@ -9,6 +9,9 @@ $end_info$ #include #include +#include +#include + // Include Xlibint.h and undefine some of its macros that clash with the standard library #include #undef min @@ -24,7 +27,6 @@ $end_info$ #include #include "thunks.inl" -#include "callback_unpacks.inl" #include "function_packs.inl" #include "function_packs_public.inl" @@ -54,6 +56,63 @@ extern "C" { return rv; } + char* XSetICValues(XIC ic, ...) { + fprintf(stderr, "XSetICValues\n"); + va_list ap; + std::vector args; + va_start(ap, ic); + for (;;) { + auto arg = va_arg(ap, unsigned long); + if (arg == 0) + break; + args.push_back(arg); + fprintf(stderr, "%016lX\n", arg); + } + + va_end(ap); + auto rv = fexfn_pack_XSetICValues_internal(ic, args.size(), &args[0]); + fprintf(stderr, "RV: %p\n", rv); + return rv; + } + + char* XGetIMValues(XIM ic, ...) { + fprintf(stderr, "XGetIMValues\n"); + va_list ap; + std::vector args; + va_start(ap, ic); + for (;;) { + auto arg = va_arg(ap, void*); + if (arg == 0) + break; + args.push_back(arg); + fprintf(stderr, "%p\n", arg); + } + + va_end(ap); + auto rv = fexfn_pack_XGetIMValues_internal(ic, args.size(), &args[0]); + fprintf(stderr, "RV: %p\n", rv); + return rv; + } + + char* XSetIMValues(XIM ic, ...) { + fprintf(stderr, "XSetIMValues\n"); + va_list ap; + std::vector args; + va_start(ap, ic); + for (;;) { + auto arg = va_arg(ap, void*); + if (arg == 0) + break; + args.push_back(arg); + fprintf(stderr, "%p\n", arg); + } + + va_end(ap); + auto rv = fexfn_pack_XSetIMValues_internal(ic, args.size(), &args[0]); + fprintf(stderr, "RV: %p\n", rv); + return rv; + } + _XIC* XCreateIC(XIM im, ...) { fprintf(stderr, "XCreateIC\n"); va_list ap; @@ -73,6 +132,25 @@ extern "C" { return rv; } + XVaNestedList XVaCreateNestedList(int unused_arg, ...) { + fprintf(stderr, "XVaCreateNestedList\n"); + va_list ap; + std::vector args; + va_start(ap, unused_arg); + for (;;) { + auto arg = va_arg(ap, void*); + if (arg == 0) + break; + args.push_back(arg); + fprintf(stderr, "%p\n", arg); + } + + va_end(ap); + auto rv = fexfn_pack_XVaCreateNestedList_internal(unused_arg, args.size(), &args[0]); + fprintf(stderr, "RV: %p\n", rv); + return rv; + } + static void LockMutexFunction(LockInfoPtr) { fprintf(stderr, "libX11: LockMutex\n"); } @@ -109,9 +187,84 @@ extern "C" { } } + Display* XOpenDisplay(const char* name) { + auto ret = fexfn_pack_XOpenDisplay(name); + + for (auto& funcptr : ret->event_vec) { + if (!funcptr) { + continue; + } + MakeHostFunctionGuestCallable(funcptr); + } + + for (auto& funcptr : ret->wire_vec) { + if (!funcptr) { + continue; + } + MakeHostFunctionGuestCallable(funcptr); + } + + MakeHostFunctionGuestCallable(ret->resource_alloc); + MakeHostFunctionGuestCallable(ret->idlist_alloc); + MakeHostFunctionGuestCallable(ret->exit_handler); + + return ret; + } + + Status _XReply(Display* display, xReply* reply, int extra, Bool discard) { + for(auto handler = display->async_handlers; handler; handler = handler->next) { + // Make host-callable and overwrite in-place + // NOTE: This data seems to be stack-allocated specifically for XReply usually, so it's *probably* safe to overwrite + handler->handler = AllocateHostTrampolineForGuestFunction(handler->handler); + } + return fexfn_pack__XReply(display, reply, extra, discard); + } + + static int _XInitDisplayLock(Display* display) { + MakeHostFunctionGuestCallable(display->lock_fns->lock_display); + MakeHostFunctionGuestCallable(display->lock_fns->unlock_display); + return 0; + } + + Status XInitThreads() { + return fexfn_pack_XInitThreadsInternal((uintptr_t)_XInitDisplayLock, (uintptr_t)CallbackUnpack::Unpack); + } + + // Register the host function pointers written by _XInitImageFuncPtrs (and + // its callers) to be guest-callable + static void FixupImageFuncPtrs(XImage* image) { + image->f.create_image = XCreateImage; + MakeHostFunctionGuestCallable(image->f.destroy_image); + MakeHostFunctionGuestCallable(image->f.get_pixel); + MakeHostFunctionGuestCallable(image->f.put_pixel); + // TODO: image->f.sub_image + MakeHostFunctionGuestCallable(image->f.add_pixel); + } + + void _XInitImageFuncPtrs(XImage* image) { + fexfn_pack__XInitImageFuncPtrs(image); + FixupImageFuncPtrs(image); + } + + XImage *XCreateImage( + Display* display, Visual* visual, + unsigned int depth, int format, + int offset, char* data, + unsigned int width, unsigned int height, + int pad, int bpp) { + auto ret = fexfn_pack_XCreateImage(display, visual, depth, format, offset, data, width, height, pad, bpp); + FixupImageFuncPtrs(ret); + return ret; + } + + Status XInitImage(XImage* image) { + auto ret = fexfn_pack_XInitImage(image); + FixupImageFuncPtrs(image); + return ret; + } + void (*_XLockMutex_fn)(LockInfoPtr) = LockMutexFunction; void (*_XUnlockMutex_fn)(LockInfoPtr) = UnlockMutexFunction; - typedef struct _LockInfoRec *LockInfoPtr; LockInfoPtr _Xglobal_lock = (LockInfoPtr)0x4142434445464748ULL; } diff --git a/ThunkLibs/libX11/libX11_Host.cpp b/ThunkLibs/libX11/libX11_Host.cpp index a5a635d30..56a386937 100644 --- a/ThunkLibs/libX11/libX11_Host.cpp +++ b/ThunkLibs/libX11/libX11_Host.cpp @@ -9,10 +9,20 @@ $end_info$ #include #include +#include +#undef min +#undef max + #include #include + +#include + +#include + #include "common/Host.h" #include +#include #include "ldr_ptrs.inl" @@ -32,8 +42,6 @@ _XIC *fexfn_impl_libX11_XCreateIC_internal(XIM a_0, size_t count, unsigned long } } -static char ErrorReply[] = "FEX: Unable to match arg count"; - char* fexfn_impl_libX11_XGetICValues_internal(XIC a_0, size_t count, unsigned long *list) { switch(count) { case 0: return fexldr_ptr_libX11_XGetICValues(a_0, nullptr); break; @@ -45,11 +53,79 @@ char* fexfn_impl_libX11_XGetICValues_internal(XIC a_0, size_t count, unsigned lo case 6: return fexldr_ptr_libX11_XGetICValues(a_0, list[0], list[1], list[2], list[3], list[4], list[5], nullptr); break; case 7: return fexldr_ptr_libX11_XGetICValues(a_0, list[0], list[1], list[2], list[3], list[4], list[5], list[6], nullptr); break; default: - fprintf(stderr, "XCreateIC_internal FAILURE\n"); - return ErrorReply; + fprintf(stderr, "XGetICValues_internal FAILURE\n"); + abort(); } } +char* fexfn_impl_libX11_XSetICValues_internal(XIC a_0, size_t count, unsigned long *list) { + switch(count) { + case 0: return fexldr_ptr_libX11_XSetICValues(a_0, nullptr); break; + case 1: return fexldr_ptr_libX11_XSetICValues(a_0, list[0], nullptr); break; + case 2: return fexldr_ptr_libX11_XSetICValues(a_0, list[0], list[1], nullptr); break; + case 3: return fexldr_ptr_libX11_XSetICValues(a_0, list[0], list[1], list[2], nullptr); break; + case 4: return fexldr_ptr_libX11_XSetICValues(a_0, list[0], list[1], list[2], list[3], nullptr); break; + case 5: return fexldr_ptr_libX11_XSetICValues(a_0, list[0], list[1], list[2], list[3], list[4], nullptr); break; + case 6: return fexldr_ptr_libX11_XSetICValues(a_0, list[0], list[1], list[2], list[3], list[4], list[5], nullptr); break; + case 7: return fexldr_ptr_libX11_XSetICValues(a_0, list[0], list[1], list[2], list[3], list[4], list[5], list[6], nullptr); break; + default: + fprintf(stderr, "XSetICValues_internal FAILURE\n"); + abort(); + } +} + +char* fexfn_impl_libX11_XGetIMValues_internal(XIM a_0, size_t count, void **list) { + switch(count) { + case 0: return fexldr_ptr_libX11_XGetIMValues(a_0, nullptr); break; + case 1: return fexldr_ptr_libX11_XGetIMValues(a_0, list[0], nullptr); break; + case 2: return fexldr_ptr_libX11_XGetIMValues(a_0, list[0], list[1], nullptr); break; + case 3: return fexldr_ptr_libX11_XGetIMValues(a_0, list[0], list[1], list[2], nullptr); break; + case 4: return fexldr_ptr_libX11_XGetIMValues(a_0, list[0], list[1], list[2], list[3], nullptr); break; + case 5: return fexldr_ptr_libX11_XGetIMValues(a_0, list[0], list[1], list[2], list[3], list[4], nullptr); break; + case 6: return fexldr_ptr_libX11_XGetIMValues(a_0, list[0], list[1], list[2], list[3], list[4], list[5], nullptr); break; + case 7: return fexldr_ptr_libX11_XGetIMValues(a_0, list[0], list[1], list[2], list[3], list[4], list[5], list[6], nullptr); break; + default: + fprintf(stderr, "XGetIMValues_internal FAILURE\n"); + abort(); + } +} + +char* fexfn_impl_libX11_XSetIMValues_internal(XIM a_0, size_t count, void **list) { + switch(count) { + case 0: return fexldr_ptr_libX11_XSetIMValues(a_0, nullptr); break; + case 1: return fexldr_ptr_libX11_XSetIMValues(a_0, list[0], nullptr); break; + case 2: return fexldr_ptr_libX11_XSetIMValues(a_0, list[0], list[1], nullptr); break; + case 3: return fexldr_ptr_libX11_XSetIMValues(a_0, list[0], list[1], list[2], nullptr); break; + case 4: return fexldr_ptr_libX11_XSetIMValues(a_0, list[0], list[1], list[2], list[3], nullptr); break; + case 5: return fexldr_ptr_libX11_XSetIMValues(a_0, list[0], list[1], list[2], list[3], list[4], nullptr); break; + case 6: return fexldr_ptr_libX11_XSetIMValues(a_0, list[0], list[1], list[2], list[3], list[4], list[5], nullptr); break; + case 7: return fexldr_ptr_libX11_XSetIMValues(a_0, list[0], list[1], list[2], list[3], list[4], list[5], list[6], nullptr); break; + default: + fprintf(stderr, "XSetIMValues_internal FAILURE\n"); + abort(); + } +} + +XVaNestedList fexfn_impl_libX11_XVaCreateNestedList_internal(int unused_arg, size_t count, void** list) { + switch(count) { + case 0: return fexldr_ptr_libX11_XVaCreateNestedList(unused_arg, nullptr); break; + case 1: return fexldr_ptr_libX11_XVaCreateNestedList(unused_arg, list[0], nullptr); break; + case 2: return fexldr_ptr_libX11_XVaCreateNestedList(unused_arg, list[0], list[1], nullptr); break; + case 3: return fexldr_ptr_libX11_XVaCreateNestedList(unused_arg, list[0], list[1], list[2], nullptr); break; + case 4: return fexldr_ptr_libX11_XVaCreateNestedList(unused_arg, list[0], list[1], list[2], list[3], nullptr); break; + case 5: return fexldr_ptr_libX11_XVaCreateNestedList(unused_arg, list[0], list[1], list[2], list[3], list[4], nullptr); break; + case 6: return fexldr_ptr_libX11_XVaCreateNestedList(unused_arg, list[0], list[1], list[2], list[3], list[4], list[5], nullptr); break; + case 7: return fexldr_ptr_libX11_XVaCreateNestedList(unused_arg, list[0], list[1], list[2], list[3], list[4], list[5], list[6], nullptr); break; + default: + fprintf(stderr, "XVaCreateNestedList_internal FAILURE\n"); + abort(); + } +} + +Status fexfn_impl_libX11_XInitThreadsInternal(uintptr_t, uintptr_t); + +Status fexfn_impl_libX11__XReply(Display*, xReply*, int, Bool); + #include "function_unpacks.inl" static ExportEntry exports[] = { @@ -59,4 +135,28 @@ static ExportEntry exports[] = { #include "ldr.inl" +static int (*ACTUAL_XInitDisplayLock_fn)(Display*) = nullptr; +static int (*INTERNAL_XInitDisplayLock_fn)(Display*) = nullptr; + +static int _XInitDisplayLock(Display* display) { + auto ret = ACTUAL_XInitDisplayLock_fn(display); + INTERNAL_XInitDisplayLock_fn(display); + return ret; +} + +Status fexfn_impl_libX11_XInitThreadsInternal(uintptr_t GuestTarget, uintptr_t GuestUnpacker) { + auto ret = fexldr_ptr_libX11_XInitThreads(); + auto _XInitDisplayLock_fn = (int(**)(Display*))dlsym(fexldr_ptr_libX11_so, "_XInitDisplayLock_fn"); + ACTUAL_XInitDisplayLock_fn = std::exchange(*_XInitDisplayLock_fn, _XInitDisplayLock); + MakeHostTrampolineForGuestFunctionAt(GuestTarget, GuestUnpacker, &INTERNAL_XInitDisplayLock_fn); + return ret; +} + +Status fexfn_impl_libX11__XReply(Display* display, xReply* reply, int extra, Bool discard) { + for(auto handler = display->async_handlers; handler; handler = handler->next) { + FinalizeHostTrampolineForGuestFunction(handler->handler); + } + return fexldr_ptr_libX11__XReply(display, reply, extra, discard); +} + EXPORTS(libX11) diff --git a/ThunkLibs/libX11/libX11_interface.cpp b/ThunkLibs/libX11/libX11_interface.cpp index 9f6e71fd7..a52638abd 100644 --- a/ThunkLibs/libX11/libX11_interface.cpp +++ b/ThunkLibs/libX11/libX11_interface.cpp @@ -4,14 +4,23 @@ #include #include +#include + +#include + #include #include +#include + template struct fex_gen_config { unsigned version = 6; }; +template +struct fex_gen_type {}; + template<> struct fex_gen_config {}; template<> struct fex_gen_config {}; template<> struct fex_gen_config {}; @@ -209,7 +218,7 @@ template<> struct fex_gen_config {}; template<> struct fex_gen_config<_XReadEvents> {}; -template<> struct fex_gen_config {}; // TODO: Fixup vtable for guest use +template<> struct fex_gen_config : fexgen::custom_guest_entrypoint {}; template<> struct fex_gen_config {}; template<> struct fex_gen_config {}; template<> struct fex_gen_config {}; @@ -437,11 +446,17 @@ template<> struct fex_gen_config { using uniform_va_type = unsigned long; }; +template<> struct fex_gen_config { + using uniform_va_type = unsigned long; +}; + template<> struct fex_gen_config { using uniform_va_type = unsigned long; }; template<> struct fex_gen_config {}; +template<> struct fex_gen_config {}; + // TODO: Make returned function pointer guest-callable. For now, just pretend it already is. template<> struct fex_gen_config : fexgen::returns_guest_pointer {}; template<> struct fex_gen_config {}; @@ -449,7 +464,17 @@ template<> struct fex_gen_config {}; template<> struct fex_gen_config {}; template<> struct fex_gen_config {}; template<> struct fex_gen_config {}; -template<> struct fex_gen_config {}; + +template<> struct fex_gen_type {}; // XDisplay::resource_alloc + +// NOTE: only indirect calls to this are allowed +// NOTE: The char* and int arguments are only present in some configurations, but always enabling them interfers with our internal ABI... +template<> struct fex_gen_type {}; // XDisplay::lock_fns->lock_display + +template<> struct fex_gen_type {}; // XDisplay::idlist_alloc +template<> struct fex_gen_type> {}; // XDisplay::exit_handler +template<> struct fex_gen_config : fexgen::custom_guest_entrypoint {}; + template<> struct fex_gen_config {}; template<> struct fex_gen_config {}; template<> struct fex_gen_config {}; @@ -473,7 +498,12 @@ template<> struct fex_gen_config {}; template<> struct fex_gen_config {}; template<> struct fex_gen_config {}; template<> struct fex_gen_config {}; -template<> struct fex_gen_config {}; + +// Sets up vtables for various objects +Status XInitThreadsInternal(uintptr_t OnXInitDisplayLock, uintptr_t OnXInitDisplayLockPacker); +template<> struct fex_gen_config : fexgen::custom_guest_entrypoint {}; +template<> struct fex_gen_config : fexgen::custom_host_impl, fexgen::custom_guest_entrypoint {}; + template<> struct fex_gen_config {}; template<> struct fex_gen_config {}; template<> struct fex_gen_config {}; @@ -522,5 +552,81 @@ template<> struct fex_gen_config {}; template<> struct fex_gen_config {}; template<> struct fex_gen_config {}; template<> struct fex_gen_config {}; -template<> struct fex_gen_config {}; +template<> struct fex_gen_config : fexgen::custom_guest_entrypoint {}; template<> struct fex_gen_config {}; + +template<> struct fex_gen_config {}; +template<> struct fex_gen_config {}; +template<> struct fex_gen_config {}; +template<> struct fex_gen_config {}; +template<> struct fex_gen_config {}; +template<> struct fex_gen_config {}; +template<> struct fex_gen_config {}; +template<> struct fex_gen_config {}; +template<> struct fex_gen_config {}; +template<> struct fex_gen_config {}; +template<> struct fex_gen_config {}; + +template<> struct fex_gen_config : fexgen::returns_guest_pointer {}; +template<> struct fex_gen_config { + using uniform_va_type = void*; +}; + +template<> struct fex_gen_config { + using uniform_va_type = void*; +}; + +template<> struct fex_gen_config : fexgen::returns_guest_pointer {}; +template<> struct fex_gen_config : fexgen::returns_guest_pointer {}; +template<> struct fex_gen_config : fexgen::returns_guest_pointer {}; +template<> struct fex_gen_config : fexgen::returns_guest_pointer {}; +template<> struct fex_gen_config : fexgen::returns_guest_pointer {}; +template<> struct fex_gen_config : fexgen::returns_guest_pointer {}; +template<> struct fex_gen_config : fexgen::returns_guest_pointer {}; +template<> struct fex_gen_config : fexgen::returns_guest_pointer {}; +template<> struct fex_gen_config : fexgen::returns_guest_pointer {}; +template<> struct fex_gen_config : fexgen::returns_guest_pointer {}; +template<> struct fex_gen_config : fexgen::returns_guest_pointer {}; + + +// TODO: Should probably catch these with a custom handler +template<> struct fex_gen_config : fexgen::returns_guest_pointer {}; +template<> struct fex_gen_config : fexgen::returns_guest_pointer {}; + +template<> struct fex_gen_config : fexgen::returns_guest_pointer {}; + +template<> struct fex_gen_config<_XRead32> {}; +template<> struct fex_gen_config<_XRead> {}; +template<> struct fex_gen_config<_XReadPad> {}; +template<> struct fex_gen_config<_XData32> {}; +template<> struct fex_gen_config<_XEatData> {}; +template<> struct fex_gen_config<_XEatDataWords> {}; + +template<> struct fex_gen_type {}; // XDisplay::async_handlers->handler +template<> struct fex_gen_config<_XReply> : fexgen::custom_host_impl, fexgen::custom_guest_entrypoint {}; + +template<> struct fex_gen_config<_XGetAsyncReply> {}; +template<> struct fex_gen_config<_XSend> {}; +template<> struct fex_gen_config<_XFlush> {}; +template<> struct fex_gen_config<_XFlushGCCache> {}; +template<> struct fex_gen_config<_XAllocScratch> {}; +template<> struct fex_gen_config<_XGetRequest> {}; +template<> struct fex_gen_config<_XGetBitsPerPixel> {}; +template<> struct fex_gen_config<_XGetScanlinePad> {}; +template<> struct fex_gen_config<_XVIDtoVisual> {}; +template<> struct fex_gen_config<_XSetLastRequestRead> {}; +template<> struct fex_gen_config<_XDeqAsyncHandler> {}; +template<> struct fex_gen_config<_XAllocTemp> {}; +template<> struct fex_gen_config<_XFreeTemp> {}; +template<> struct fex_gen_config<_XUnknownNativeEvent> {}; +template<> struct fex_gen_config<_XIsEventCookie> {}; + +template<> struct fex_gen_type {}; // XImage::f.destroy_image +template<> struct fex_gen_type {}; // XImage::f.get_pixel +template<> struct fex_gen_type {}; // XImage::f.put_pixel +template<> struct fex_gen_type {}; // XImage::f.add_pixel +template<> struct fex_gen_config<_XInitImageFuncPtrs> : fexgen::custom_guest_entrypoint {}; + +template<> struct fex_gen_config { + using uniform_va_type = void*; +}; diff --git a/ThunkLibs/libXext/libXext_Guest.cpp b/ThunkLibs/libXext/libXext_Guest.cpp index 1f80fcbe4..26b1af826 100644 --- a/ThunkLibs/libXext/libXext_Guest.cpp +++ b/ThunkLibs/libXext/libXext_Guest.cpp @@ -40,7 +40,6 @@ extern "C" { #include "common/Guest.h" #include "thunks.inl" -#include "callback_unpacks.inl" #include "function_packs.inl" #include "function_packs_public.inl" diff --git a/ThunkLibs/libvulkan/Guest.cpp b/ThunkLibs/libvulkan/Guest.cpp index 64a80ada9..e9e1567b4 100644 --- a/ThunkLibs/libvulkan/Guest.cpp +++ b/ThunkLibs/libvulkan/Guest.cpp @@ -29,9 +29,9 @@ extern "C" { // linked to the corresponding host function pointer const std::unordered_map HostPtrInvokers = std::invoke([]() { -#define PAIR(name, unused) Ret[#name] = reinterpret_cast(GetCallerForHostThunkFromRuntimePointer(name)); +#define PAIR(name, unused) Ret[#name] = reinterpret_cast(GetCallerForHostFunction(name)); std::unordered_map Ret; - FOREACH_internal_SYMBOL(PAIR) + FOREACH_internal_SYMBOL(PAIR); return Ret; #undef PAIR }); @@ -53,7 +53,7 @@ static PFN_vkVoidFunction MakeGuestCallable(const char* origin, PFN_vkVoidFuncti if (It == HostPtrInvokers.end()) { fprintf(stderr, "%s: Unknown Vulkan function at address %p: %s\n", origin, func, name); if (stub_unknown_functions) { - const auto StubHostPtrInvoker = CallHostThunkFromRuntimePointer; + const auto StubHostPtrInvoker = CallHostFunction; LinkAddressToFunction((uintptr_t)func, reinterpret_cast(StubHostPtrInvoker)); return func; } diff --git a/ThunkLibs/libxcb/libxcb_Guest.cpp b/ThunkLibs/libxcb/libxcb_Guest.cpp index 1ce77f368..f79103247 100644 --- a/ThunkLibs/libxcb/libxcb_Guest.cpp +++ b/ThunkLibs/libxcb/libxcb_Guest.cpp @@ -29,7 +29,6 @@ $end_info$ #include "thunks.inl" #include "function_packs.inl" #include "function_packs_public.inl" -#include "callback_unpacks.inl" static std::thread CBThread{}; static std::atomic CBDone{false}; diff --git a/unittests/ThunkLibs/generator.cpp b/unittests/ThunkLibs/generator.cpp index f85e78e34..ee7c1f41e 100644 --- a/unittests/ThunkLibs/generator.cpp +++ b/unittests/ThunkLibs/generator.cpp @@ -57,7 +57,6 @@ struct Fixture { tmpdir + "/thunks", tmpdir + "/function_packs", tmpdir + "/function_packs_public", - tmpdir + "/callback_unpacks", }; } @@ -280,12 +279,16 @@ SourceWithAST Fixture::run_thunkgen_guest(std::string_view prelude, std::string_ std::string result = "#include \n" "#define MAKE_THUNK(lib, name, hash) extern \"C\" int fexthunks_##lib##_##name(void*);\n" + "template\n" + "struct callback_thunk_defined;\n" + "#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, output_filenames.function_packs_public, output_filenames.function_packs, }) { @@ -321,13 +324,19 @@ SourceWithAST Fixture::run_thunkgen_host(std::string_view prelude, std::string_v " using args_t = typename function_traits::arg_t;\n" " return Fn(reinterpret_cast(argsv));\n" "}\n" - "#define LOAD_INTERNAL_GUESTPTR_ARG(arg)\n" + "#define LOAD_INTERNAL_GUESTPTR_VIA_CUSTOM_ABI(arg)\n" "struct GuestcallInfo {\n" " uintptr_t HostPacker;\n" " void (*CallCallback)(uintptr_t, uintptr_t, void*);\n" " uintptr_t GuestUnpacker;\n" " uintptr_t GuestTarget;\n" - "};\n"; + "};\n" + "template\n" + "struct CallbackUnpack {\n" + " static void ForIndirectCall(void* argsv);\n" + "};\n" + "template\n" + "void FinalizeHostTrampolineForGuestFunction(F*);\n"; for (auto& filename : { output_filenames.ldr_ptrs, @@ -414,6 +423,31 @@ TEST_CASE_METHOD(Fixture, "VersionedLibrary") { })); } +TEST_CASE_METHOD(Fixture, "FunctionPointerViaType") { + const auto output = run_thunkgen("", + "template struct fex_gen_type {};\n" + "template<> struct fex_gen_type {};\n"); + + // Guest should apply MAKE_CALLBACK_THUNK to this signature + CHECK_THAT(output.guest, + matches(classTemplateSpecializationDecl( + // Should have signature matching input function + hasName("callback_thunk_defined"), + hasTemplateArgument(0, refersToType(asString("int (char, char)"))) + ))); + + // Host should export the unpacking function for callback arguments + CHECK_THAT(output.host, + matches(varDecl( + hasName("exports"), + hasType(constantArrayType(hasElementType(asString("struct ExportEntry")), hasSize(2))), + hasInitializer(hasDescendant(declRefExpr(to(cxxMethodDecl(hasName("ForIndirectCall"), ofClass(hasName("CallbackUnpack"))).bind("funcptr"))))) + )).check_binding("funcptr", +[](const clang::CXXMethodDecl* decl) { + auto parent = llvm::cast(decl->getParent()); + return parent->getTemplateArgs().get(0).getAsType().getAsString() == "int (char, char)"; + })); +} + // Parameter is a function pointer TEST_CASE_METHOD(Fixture, "FunctionPointerParameter") { const auto output = run_thunkgen("", @@ -427,18 +461,25 @@ 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 should export the packing function for callback arguments + // 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(3))), - hasInitializer(hasDescendant(declRefExpr(to(functionDecl(hasName("fexfn_pack_guestcall_funcCBFN")))))) + hasInitializer(hasDescendant(declRefExpr(to(cxxMethodDecl(hasName("ForIndirectCall"), ofClass(hasName("CallbackUnpack"))))))) ))); }