Thunks: Use signature-based thunking of guest function pointers

This changes how host trampolines for guest functions are created. Instead
of doing this purely on the guest-side, it's either the host-side that
creates them in a single step *or* a cooperative two-step initialization
process must be used. In the latter, trampolines are allocated and partially
initialized on the guest and must be finalized on the host before use.
This commit is contained in:
Tony Wasserka 2022-08-05 17:41:25 +02:00
parent 3ac1650001
commit e8ad1ca0a0
7 changed files with 231 additions and 190 deletions

View File

@ -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<IR::SHA256Sum, ThunkedFunction*, TruncatingSHA256Hash> 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_t*>(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_t*>(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<ThunkHandler_impl *>(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<uintptr_t>(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<uintptr_t>(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<HostToGuestTrampolinePtr* const>(ThunkHandler->HostTrampolineInstanceDataPtr);
ThunkHandler->HostTrampolineInstanceDataAvailable -= HostToGuestTrampolineSize;
ThunkHandler->HostTrampolineInstanceDataPtr += HostToGuestTrampolineSize;
memcpy(HostTrampoline, (void*)&HostToGuestTrampolineTemplate, HostToGuestTrampolineSize);
GetInstanceInfo(HostTrampoline) = TrampolineInstanceInfo {
.HostPacker = reinterpret_cast<void*>(HostPackerEntry->second),
.CallCallback = (uintptr_t)&CallCallback,
.GuestUnpacker = args->GuestUnpacker,
.GuestTarget = args->GuestTarget
};
args->rv = reinterpret_cast<uintptr_t>(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<ThunkHandler_impl *>(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<HostToGuestTrampolinePtr* const>(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;
}
}
}

View File

@ -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/

View File

@ -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<fn_t*>(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()) {

View File

@ -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 "$<TARGET_PROPERTY:${NAME}-deps,INTERFACE_INCLUDE_DIRECTORIES>")

View File

@ -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<typename Target>
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<typename F>
struct CallbackUnpack;
template<typename Result, typename... Args>
struct CallbackUnpack<Result(Args...)> {
static void Unpack(uintptr_t cb, void* argsv) {
using fn_t = Result(Args...);
auto callback = reinterpret_cast<fn_t*>(cb);
auto args = reinterpret_cast<PackedArguments<Result, Args...>*>(argsv);
Invoke(callback, *args);
}
};
template<typename Result, typename... Args>
struct CallbackUnpack<Result(*)(Args...)> : CallbackUnpack<Result(Args...)> {
};
template<typename Target>
inline Target *AllocateHostTrampolineForGuestFunction(Target *GuestTarget) {
return AllocateHostTrampolineForGuestFunction(CallbackUnpack<Target*>::Unpack,
GuestTarget);
}
inline bool IsHostHeapAllocation(void* ptr) {
struct {
void* ptr;

View File

@ -5,10 +5,35 @@ $end_info$
*/
#pragma once
#include <stdint.h>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#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<typename Fn>
struct function_traits;
template<typename Result, typename Arg>
@ -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<typename>
@ -84,6 +112,19 @@ struct CallbackUnpack;
template<typename Result, typename... Args>
struct CallbackUnpack<Result(Args...)> {
static Result CallGuestPtr(Args... args) {
GuestcallInfo *guestcall;
LOAD_INTERNAL_GUESTPTR_VIA_CUSTOM_ABI(guestcall);
PackedArguments<Result, Args...> packed_args = {
args...
};
guestcall->CallCallback(guestcall->GuestUnpacker, guestcall->GuestTarget, &packed_args);
if constexpr (!std::is_void_v<Result>) {
return packed_args.rv;
}
}
static void ForIndirectCall(void* argsv) {
auto args = reinterpret_cast<PackedArguments<Result, Args..., uintptr_t>*>(argsv);
constexpr auto CBIndex = sizeof...(Args);
@ -133,6 +174,17 @@ struct CallbackUnpack<Result(Args...)> {
}
};
template<typename Result, typename... Args>
struct CallbackUnpack<Result(*)(Args...)> : CallbackUnpack<Result(Args...)> {
};
template<typename FuncType>
void MakeHostTrampolineForGuestFunctionAt(uintptr_t GuestTarget, uintptr_t GuestUnpacker, FuncType **Func) {
*Func = (FuncType*)FEXCore::MakeHostTrampolineForGuestFunction(
(void*)&CallbackUnpack<FuncType>::CallGuestPtr,
GuestTarget,
GuestUnpacker);
}
template<typename F>
void FinalizeHostTrampolineForGuestFunction(F* PreallocatedTrampolineForGuestFunction) {
FEXCore::FinalizeHostTrampolineForGuestFunction(
(FEXCore::HostToGuestTrampolinePtr*)PreallocatedTrampolineForGuestFunction,
(void*)&CallbackUnpack<F>::CallGuestPtr);
}

View File

@ -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<sig> {};\n"
"#define FEX_PACKFN_LINKAGE\n"
"template<typename Target>\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<typename Target>\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<typename>\n"
"struct CallbackUnpack {\n"
" static void ForIndirectCall(void* argsv);\n"
"};\n";
"};\n"
"template<typename F>\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")))))))
)));
}