mirror of
https://github.com/FEX-Emu/FEX.git
synced 2025-02-14 03:30:46 +00:00
Thunks/gen: Add support for generation of host library functions
This commit is contained in:
parent
22466a973c
commit
bfb9cabeb8
@ -141,6 +141,35 @@ void FrontendAction::EndSourceFileAction() {
|
||||
}
|
||||
};
|
||||
|
||||
auto format_struct_members = [](const FunctionParams& params, const char* indent) {
|
||||
std::string ret;
|
||||
for (std::size_t idx = 0; idx < params.param_types.size(); ++idx) {
|
||||
ret += indent + format_decl(params.param_types[idx].getUnqualifiedType(), "a_" + std::to_string(idx)) + ";\n";
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
|
||||
auto format_function_args = [](const FunctionParams& params) {
|
||||
std::string ret;
|
||||
for (std::size_t idx = 0; idx < params.param_types.size(); ++idx) {
|
||||
ret += "args->a_" + std::to_string(idx) + ", ";
|
||||
}
|
||||
// drop trailing ", "
|
||||
ret.resize(ret.size() > 2 ? ret.size() - 2 : 0);
|
||||
return ret;
|
||||
};
|
||||
|
||||
auto format_function_params = [](const FunctionParams& params) {
|
||||
std::string ret;
|
||||
for (std::size_t idx = 0; idx < params.param_types.size(); ++idx) {
|
||||
auto& type = params.param_types[idx];
|
||||
ret += format_decl(type, "a_" + std::to_string(idx)) + ", ";
|
||||
}
|
||||
// drop trailing ", "
|
||||
ret.resize(ret.size() > 2 ? ret.size() - 2 : 0);
|
||||
return ret;
|
||||
};
|
||||
|
||||
auto get_sha256 = [this](const std::string& function_name) {
|
||||
std::string sha256_message = libname + ":" + function_name;
|
||||
std::vector<unsigned char> sha256(SHA256_DIGEST_LENGTH);
|
||||
@ -223,6 +252,71 @@ void FrontendAction::EndSourceFileAction() {
|
||||
}
|
||||
file << "}\n";
|
||||
}
|
||||
|
||||
if (!output_filenames.function_unpacks.empty()) {
|
||||
std::ofstream file(output_filenames.function_unpacks);
|
||||
|
||||
file << "extern \"C\" {\n";
|
||||
for (auto& thunk : thunks) {
|
||||
const auto& function_name = thunk.function_name;
|
||||
bool is_void = thunk.return_type->isVoidType();
|
||||
|
||||
file << "struct fexfn_packed_args_" << libname << "_" << function_name << " {\n";
|
||||
file << format_struct_members(thunk, " ");
|
||||
if (!is_void) {
|
||||
file << " " << format_decl(thunk.return_type, "rv") << ";\n";
|
||||
} else if (thunk.param_types.size() == 0) {
|
||||
// Avoid "empty struct has size 0 in C, size 1 in C++" warning
|
||||
file << " char force_nonempty;\n";
|
||||
}
|
||||
file << "};\n";
|
||||
|
||||
file << "static void fexfn_unpack_" << libname << "_" << function_name << "(fexfn_packed_args_" << libname << "_" << function_name << "* args) {\n";
|
||||
file << (is_void ? " " : " args->rv = ") << "fexldr_ptr_" << libname << "_" << function_name << "(" << format_function_args(thunk) << ");\n";
|
||||
file << "}\n";
|
||||
}
|
||||
|
||||
file << "}\n";
|
||||
}
|
||||
|
||||
if (!output_filenames.tab_function_unpacks.empty()) {
|
||||
std::ofstream file(output_filenames.tab_function_unpacks);
|
||||
|
||||
for (auto& thunk : thunks) {
|
||||
const auto& function_name = thunk.function_name;
|
||||
auto sha256 = get_sha256(function_name);
|
||||
|
||||
file << "{(uint8_t*)\"";
|
||||
for (auto c : sha256) {
|
||||
file << "\\x" << std::hex << std::setw(2) << std::setfill('0') << +c;
|
||||
}
|
||||
file << "\", &fexfn_type_erased_unpack<fexfn_unpack_" << libname << "_" << function_name << ">}, // " << libname << ":" << function_name << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
if (!output_filenames.ldr.empty()) {
|
||||
std::ofstream file(output_filenames.ldr);
|
||||
|
||||
file << "static void* fexldr_ptr_" << libname << "_so;\n";
|
||||
file << "extern \"C\" bool fexldr_init_" << libname << "() {\n";
|
||||
file << " fexldr_ptr_" << libname << "_so = dlopen(\"" << libname << ".so\", RTLD_LOCAL | RTLD_LAZY);\n";
|
||||
file << " if (!fexldr_ptr_" << libname << "_so) { return false; }\n\n";
|
||||
for (auto& import : thunked_api) {
|
||||
file << " (void*&)fexldr_ptr_" << libname << "_" << import.function_name << " = dlsym(fexldr_ptr_" << libname << "_so, \"" << import.function_name << "\");\n";
|
||||
}
|
||||
file << " return true;\n";
|
||||
file << "}\n";
|
||||
}
|
||||
|
||||
if (!output_filenames.ldr_ptrs.empty()) {
|
||||
std::ofstream file(output_filenames.ldr_ptrs);
|
||||
|
||||
for (auto& import : thunked_api) {
|
||||
const auto& function_name = import.function_name;
|
||||
file << "using fexldr_type_" << libname << "_" << function_name << " = auto " << "(" << format_function_params(import) << ") -> " << import.return_type.getAsString() << ";\n";
|
||||
file << "static fexldr_type_" << libname << "_" << function_name << " *fexldr_ptr_" << libname << "_" << function_name << ";\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<clang::ASTConsumer> FrontendAction::CreateASTConsumer(clang::CompilerInstance&, clang::StringRef) {
|
||||
|
@ -4,6 +4,12 @@
|
||||
#include <string>
|
||||
|
||||
struct OutputFilenames {
|
||||
// Host
|
||||
std::string function_unpacks;
|
||||
std::string tab_function_unpacks;
|
||||
std::string ldr;
|
||||
std::string ldr_ptrs;
|
||||
|
||||
// Guest
|
||||
std::string thunks;
|
||||
std::string function_packs;
|
||||
|
@ -52,7 +52,15 @@ int main(int argc, char* argv[]) {
|
||||
while (arg < last_internal_arg) {
|
||||
auto target = std::string { *arg++ };
|
||||
auto out_filename = *arg++;
|
||||
if (target == "-thunks") {
|
||||
if (target == "-function_unpacks") {
|
||||
output_filenames.function_unpacks = out_filename;
|
||||
} else if (target == "-tab_function_unpacks") {
|
||||
output_filenames.tab_function_unpacks = out_filename;
|
||||
} else if (target == "-ldr") {
|
||||
output_filenames.ldr = out_filename;
|
||||
} else if (target == "-ldr_ptrs") {
|
||||
output_filenames.ldr_ptrs = out_filename;
|
||||
} else if (target == "-thunks") {
|
||||
output_filenames.thunks = out_filename;
|
||||
} else if (target == "-function_packs") {
|
||||
output_filenames.function_packs = out_filename;
|
||||
|
@ -7,6 +7,21 @@ $end_info$
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
|
||||
template<typename Fn>
|
||||
struct function_traits;
|
||||
template<typename Result, typename Arg>
|
||||
struct function_traits<Result(*)(Arg)> {
|
||||
using result_t = Result;
|
||||
using arg_t = Arg;
|
||||
};
|
||||
|
||||
template<auto Fn>
|
||||
static typename function_traits<decltype(Fn)>::result_t
|
||||
fexfn_type_erased_unpack(void* argsv) {
|
||||
using args_t = typename function_traits<decltype(Fn)>::arg_t;
|
||||
return Fn(reinterpret_cast<args_t>(argsv));
|
||||
}
|
||||
|
||||
struct ExportEntry { uint8_t* sha256; void(*fn)(void *); };
|
||||
|
||||
typedef void fex_call_callback_t(uintptr_t callback, void *arg0, void* arg1);
|
||||
|
@ -16,6 +16,10 @@ struct Fixture {
|
||||
tmpdir = std::tmpnam(nullptr);
|
||||
std::filesystem::create_directory(tmpdir);
|
||||
output_filenames = {
|
||||
tmpdir + "/function_unpacks",
|
||||
tmpdir + "/tab_function_unpacks",
|
||||
tmpdir + "/ldr",
|
||||
tmpdir + "/ldr_ptrs",
|
||||
tmpdir + "/thunks",
|
||||
tmpdir + "/function_packs",
|
||||
tmpdir + "/function_packs_public"
|
||||
@ -26,7 +30,14 @@ struct Fixture {
|
||||
std::filesystem::remove_all(tmpdir);
|
||||
}
|
||||
|
||||
struct GenOutput {
|
||||
std::string guest;
|
||||
std::string host;
|
||||
};
|
||||
|
||||
std::string run_thunkgen_guest(std::string_view code, bool silent = false);
|
||||
std::string run_thunkgen_host(std::string_view code, bool silent = false);
|
||||
GenOutput run_thunkgen(std::string_view code, bool silent = false);
|
||||
|
||||
const std::string libname = "libtest";
|
||||
std::string tmpdir;
|
||||
@ -210,21 +221,85 @@ std::string Fixture::run_thunkgen_guest(std::string_view code, bool silent) {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates host thunk library code from the given input
|
||||
*/
|
||||
std::string Fixture::run_thunkgen_host(std::string_view code, bool silent) {
|
||||
run_tool(std::make_unique<FrontendAction>(libname, output_filenames), code, silent);
|
||||
|
||||
std::string result =
|
||||
"#include <cstdint>\n"
|
||||
"#include <dlfcn.h>\n"
|
||||
"template<typename Fn>\n"
|
||||
"struct function_traits;\n"
|
||||
"template<typename Result, typename Arg>\n"
|
||||
"struct function_traits<Result(*)(Arg)> {\n"
|
||||
" using result_t = Result;\n"
|
||||
" using arg_t = Arg;\n"
|
||||
"};\n"
|
||||
"template<auto Fn>\n"
|
||||
"static typename function_traits<decltype(Fn)>::result_t\n"
|
||||
"fexfn_type_erased_unpack(void* argsv) {\n"
|
||||
" using args_t = typename function_traits<decltype(Fn)>::arg_t;\n"
|
||||
" return Fn(reinterpret_cast<args_t>(argsv));\n"
|
||||
"}\n";
|
||||
for (auto& filename : {
|
||||
output_filenames.ldr_ptrs,
|
||||
output_filenames.function_unpacks,
|
||||
output_filenames.tab_function_unpacks,
|
||||
output_filenames.ldr,
|
||||
}) {
|
||||
bool tab_function_unpacks = (filename == output_filenames.tab_function_unpacks);
|
||||
if (tab_function_unpacks) {
|
||||
result += "struct ExportEntry { uint8_t* sha256; void(*fn)(void *); };\n";
|
||||
result += "static ExportEntry exports[] = {\n";
|
||||
}
|
||||
|
||||
std::ifstream file(filename);
|
||||
const auto current_size = result.size();
|
||||
const auto new_data_size = std::filesystem::file_size(filename);
|
||||
result.resize(result.size() + new_data_size);
|
||||
file.read(result.data() + current_size, result.size());
|
||||
|
||||
if (tab_function_unpacks) {
|
||||
result += " { nullptr, nullptr }\n";
|
||||
result += "};\n";
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Fixture::GenOutput Fixture::run_thunkgen(std::string_view code, bool silent) {
|
||||
return { run_thunkgen_guest(code, silent),
|
||||
run_thunkgen_host(code, silent) };
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(Fixture, "Trivial") {
|
||||
const auto output = run_thunkgen_guest(
|
||||
const auto output = run_thunkgen(
|
||||
"#include <thunks_common.h>\n"
|
||||
"void func();\n"
|
||||
"template<auto> struct fex_gen_config {};\n"
|
||||
"template<> struct fex_gen_config<func> {};\n");
|
||||
|
||||
CHECK_THAT(output, DefinesPublicFunction("func"));
|
||||
// Guest code
|
||||
CHECK_THAT(output.guest, DefinesPublicFunction("func"));
|
||||
|
||||
CHECK_THAT(output,
|
||||
CHECK_THAT(output.guest,
|
||||
matches(functionDecl(
|
||||
hasName("fexfn_pack_func"),
|
||||
returns(asString("void")),
|
||||
parameterCountIs(0)
|
||||
)));
|
||||
|
||||
// Host code
|
||||
CHECK_THAT(output.host,
|
||||
matches(varDecl(
|
||||
hasName("exports"),
|
||||
hasType(constantArrayType(hasElementType(asString("struct ExportEntry")), hasSize(2))),
|
||||
hasInitializer(initListExpr(hasInit(0, expr()),
|
||||
hasInit(1, initListExpr(hasInit(0, implicitCastExpr()), hasInit(1, implicitCastExpr())))))
|
||||
// TODO: check null termination
|
||||
)));
|
||||
}
|
||||
|
||||
// Function pointer parameters trigger an error
|
||||
@ -238,17 +313,19 @@ TEST_CASE_METHOD(Fixture, "FunctionPointerParameter") {
|
||||
TEST_CASE_METHOD(Fixture, "MultipleParameters") {
|
||||
const std::string prelude = "struct TestStruct { int member; };\n";
|
||||
|
||||
auto output = run_thunkgen_guest(
|
||||
auto output = run_thunkgen(
|
||||
prelude +
|
||||
"void func(int arg, char, unsigned long, TestStruct);\n"
|
||||
"template<auto> struct fex_gen_config {};\n"
|
||||
"template<> struct fex_gen_config<func> {};\n");
|
||||
|
||||
output = prelude + output;
|
||||
output.guest = prelude + output.guest;
|
||||
output.host = prelude + output.host;
|
||||
|
||||
CHECK_THAT(output, DefinesPublicFunction("func"));
|
||||
// Guest code
|
||||
CHECK_THAT(output.guest, DefinesPublicFunction("func"));
|
||||
|
||||
CHECK_THAT(output,
|
||||
CHECK_THAT(output.guest,
|
||||
matches(functionDecl(
|
||||
hasName("fexfn_pack_func"),
|
||||
returns(asString("void")),
|
||||
@ -258,6 +335,30 @@ TEST_CASE_METHOD(Fixture, "MultipleParameters") {
|
||||
hasParameter(2, hasType(asString("unsigned long"))),
|
||||
hasParameter(3, hasType(asString("struct TestStruct")))
|
||||
)));
|
||||
|
||||
// Host code
|
||||
CHECK_THAT(output.host,
|
||||
matches(varDecl(
|
||||
hasName("exports"),
|
||||
hasType(constantArrayType(hasElementType(asString("struct ExportEntry")), hasSize(2))),
|
||||
hasInitializer(initListExpr(hasInit(0, expr()),
|
||||
hasInit(1, initListExpr(hasInit(0, implicitCastExpr()), hasInit(1, implicitCastExpr())))))
|
||||
// TODO: check null termination
|
||||
)));
|
||||
|
||||
CHECK_THAT(output.host,
|
||||
matches(functionDecl(
|
||||
hasName("fexfn_unpack_libtest_func"),
|
||||
// Packed argument struct should contain all parameters
|
||||
parameterCountIs(1),
|
||||
hasParameter(0, hasType(pointerType(pointee(
|
||||
recordType(hasDeclaration(decl(
|
||||
has(fieldDecl(hasType(asString("int")))),
|
||||
has(fieldDecl(hasType(asString("char")))),
|
||||
has(fieldDecl(hasType(asString("unsigned long")))),
|
||||
has(fieldDecl(hasType(asString("struct TestStruct"))))
|
||||
)))))))
|
||||
)));
|
||||
}
|
||||
|
||||
// Returning a function pointer should trigger an error
|
||||
|
Loading…
x
Reference in New Issue
Block a user