diff --git a/ThunkLibs/Generator/analysis.cpp b/ThunkLibs/Generator/analysis.cpp index 5ddf66dde..1b6a86b86 100644 --- a/ThunkLibs/Generator/analysis.cpp +++ b/ThunkLibs/Generator/analysis.cpp @@ -170,6 +170,8 @@ static ParameterAnnotations GetParameterAnnotations(clang::ASTContext& context, void AnalysisAction::ParseInterface(clang::ASTContext& context) { ErrorReporter report_error { context }; + const std::unordered_map no_param_annotations {}; + // TODO: Assert fex_gen_type is not declared at non-global namespaces if (auto template_decl = FindClassTemplateDeclByName(*context.getTranslationUnitDecl(), "fex_gen_type")) { for (auto* decl : template_decl->specializations()) { @@ -184,7 +186,7 @@ void AnalysisAction::ParseInterface(clang::ASTContext& context) { type = type->getLocallyUnqualifiedSingleStepDesugaredType(); if (type->isFunctionPointerType() || type->isFunctionType()) { - thunked_funcptrs.insert(type.getTypePtr()); + thunked_funcptrs[type.getAsString()] = std::pair { type.getTypePtr(), no_param_annotations }; } else { [[maybe_unused]] auto [it, inserted] = types.emplace(context.getCanonicalType(type.getTypePtr()), RepackedType { }); assert(inserted); @@ -349,7 +351,7 @@ void AnalysisAction::ParseInterface(clang::ASTContext& context) { data.callbacks.emplace(param_idx, callback); if (!callback.is_stub && !callback.is_guest && !data.custom_host_impl) { - thunked_funcptrs.insert(context.getCanonicalType(funcptr)); + thunked_funcptrs[emitted_function->getNameAsString() + "_cb" + std::to_string(param_idx)] = std::pair { context.getCanonicalType(funcptr), no_param_annotations }; } if (data.callbacks.size() != 1) { @@ -417,7 +419,7 @@ void AnalysisAction::ParseInterface(clang::ASTContext& context) { // For indirect calls, register the function signature as a function pointer type if (namespace_info.indirect_guest_calls) { - thunked_funcptrs.insert(context.getCanonicalType(emitted_function->getFunctionType())); + thunked_funcptrs[emitted_function->getNameAsString()] = std::pair { context.getCanonicalType(emitted_function->getFunctionType()), data.param_annotations }; } thunks.push_back(std::move(data)); diff --git a/ThunkLibs/Generator/analysis.h b/ThunkLibs/Generator/analysis.h index f5bf10146..bc62716f6 100644 --- a/ThunkLibs/Generator/analysis.h +++ b/ThunkLibs/Generator/analysis.h @@ -133,7 +133,9 @@ protected: std::vector thunked_api; // Set of function types for which to generate Guest->Host thunking trampolines. - std::unordered_set thunked_funcptrs; + // The map key is a unique identifier that must be consistent between guest/host processing passes. + // The map value is a pair of the function pointer's clang::Type and the mapping of parameter annotations + std::unordered_map>> thunked_funcptrs; std::unordered_map types; std::optional lib_version; diff --git a/ThunkLibs/Generator/data_layout.cpp b/ThunkLibs/Generator/data_layout.cpp index da05a6ee8..44b2eb0ff 100644 --- a/ThunkLibs/Generator/data_layout.cpp +++ b/ThunkLibs/Generator/data_layout.cpp @@ -151,10 +151,34 @@ ABI GetStableLayout(const clang::ASTContext& context, const std::unordered_map GetSha256(const std::string& function_name) { + std::array sha256; + SHA256(reinterpret_cast(function_name.data()), + function_name.size(), + sha256.data()); + return sha256; +}; + void AnalyzeDataLayoutAction::OnAnalysisComplete(clang::ASTContext& context) { if (StrictModeEnabled(context)) { type_abi = GetStableLayout(context, ComputeDataLayout(context, types)); } + + // Register functions that must be guest-callable through host function pointers + for (auto funcptr_type_it = thunked_funcptrs.begin(); funcptr_type_it != thunked_funcptrs.end(); ++funcptr_type_it) { + auto& funcptr_id = funcptr_type_it->first; + auto& [type, param_annotations] = funcptr_type_it->second; + auto func_type = type->getAs(); + std::string mangled_name = clang::QualType { type, 0 }.getAsString(); + auto cb_sha256 = GetSha256("fexcallback_" + mangled_name); + FuncPtrInfo info = { cb_sha256 }; + + info.result = func_type->getReturnType().getAsString(); + for (auto arg : func_type->getParamTypes()) { + info.args.push_back(arg.getAsString()); + } + type_abi.thunked_funcptrs[funcptr_id] = std::move(info); + } } TypeCompatibility DataLayoutCompareAction::GetTypeCompatibility( @@ -297,6 +321,10 @@ TypeCompatibility DataLayoutCompareAction::GetTypeCompatibility( return compat; } +FuncPtrInfo DataLayoutCompareAction::LookupGuestFuncPtrInfo(const char* funcptr_id) { + return guest_abi.thunked_funcptrs.at(funcptr_id); +} + DataLayoutCompareActionFactory::DataLayoutCompareActionFactory(const ABI& abi) : abi(abi) { } diff --git a/ThunkLibs/Generator/data_layout.h b/ThunkLibs/Generator/data_layout.h index 3d58357f4..da30c3d0e 100644 --- a/ThunkLibs/Generator/data_layout.h +++ b/ThunkLibs/Generator/data_layout.h @@ -84,6 +84,7 @@ struct FuncPtrInfo { }; struct ABI : std::unordered_map { + std::unordered_map thunked_funcptrs; int pointer_size; // in bytes }; @@ -111,6 +112,8 @@ public: const std::unordered_map host_abi, std::unordered_map& type_compat); + FuncPtrInfo LookupGuestFuncPtrInfo(const char* funcptr_id); + private: const ABI& guest_abi; }; diff --git a/ThunkLibs/Generator/gen.cpp b/ThunkLibs/Generator/gen.cpp index 7eb364e94..b82965166 100644 --- a/ThunkLibs/Generator/gen.cpp +++ b/ThunkLibs/Generator/gen.cpp @@ -120,8 +120,8 @@ void GenerateThunkLibsAction::OnAnalysisComplete(clang::ASTContext& context) { return ret; }; - auto get_sha256 = [this](const std::string& function_name) { - std::string sha256_message = libname + ":" + function_name; + auto get_sha256 = [this](const std::string& function_name, bool include_libname) { + std::string sha256_message = (include_libname ? libname + ":" : "") + function_name; std::vector sha256(SHA256_DIGEST_LENGTH); SHA256(reinterpret_cast(sha256_message.data()), sha256_message.size(), @@ -141,18 +141,26 @@ void GenerateThunkLibsAction::OnAnalysisComplete(clang::ASTContext& context) { file << "extern \"C\" {\n"; for (auto& thunk : thunks) { const auto& function_name = thunk.function_name; - auto sha256 = get_sha256(function_name); + auto sha256 = get_sha256(function_name, true); fmt::print( file, "MAKE_THUNK({}, {}, \"{:#02x}\")\n", libname, function_name, fmt::join(sha256, ", ")); } file << "}\n"; // Guest->Host transition points for invoking runtime host-function pointers based on their signature + std::vector> sha256s; for (auto type_it = thunked_funcptrs.begin(); type_it != thunked_funcptrs.end(); ++type_it) { - auto* type = *type_it; + auto* type = type_it->second.first; std::string funcptr_signature = clang::QualType { type, 0 }.getAsString(); - auto cb_sha256 = get_sha256("fexcallback_" + funcptr_signature); + auto cb_sha256 = get_sha256("fexcallback_" + funcptr_signature, false); + auto it = std::find(sha256s.begin(), sha256s.end(), cb_sha256); + if (it != sha256s.end()) { + // TODO: Avoid this ugly way of avoiding duplicates + continue; + } else { + sha256s.push_back(cb_sha256); + } // Thunk used for guest-side calls to host function pointers file << " // " << funcptr_signature << "\n"; @@ -382,18 +390,30 @@ void GenerateThunkLibsAction::OnAnalysisComplete(clang::ASTContext& context) { file << "static ExportEntry exports[] = {\n"; for (auto& thunk : thunks) { const auto& function_name = thunk.function_name; - auto sha256 = get_sha256(function_name); + auto sha256 = get_sha256(function_name, true); fmt::print( file, " {{(uint8_t*)\"\\x{:02x}\", (void(*)(void *))&fexfn_unpack_{}_{}}}, // {}:{}\n", fmt::join(sha256, "\\x"), libname, function_name, libname, function_name); } // Endpoints for Guest->Host invocation of runtime host-function pointers - for (auto& type : thunked_funcptrs) { + for (auto& host_funcptr_entry : thunked_funcptrs) { + auto& [type, param_annotations] = host_funcptr_entry.second; std::string mangled_name = clang::QualType { type, 0 }.getAsString(); - auto cb_sha256 = get_sha256("fexcallback_" + mangled_name); - fmt::print( file, " {{(uint8_t*)\"\\x{:02x}\", (void(*)(void *))&CallbackUnpack<{}>::ForIndirectCall}},\n", - fmt::join(cb_sha256, "\\x"), mangled_name); + auto info = LookupGuestFuncPtrInfo(host_funcptr_entry.first.c_str()); + + std::string annotations; + for (int param_idx = 0; param_idx < info.args.size(); ++param_idx) { + if (param_idx != 0) { + annotations += ", "; + } + + // TODO: Add annotations as needed + annotations += "ParameterAnnotations {}"; + } + fmt::print( file, " {{(uint8_t*)\"\\x{:02x}\", (void(*)(void *))&GuestWrapperForHostFunction<{}({})>::Call<{}>}}, // {}\n", + fmt::join(info.sha256, "\\x"), info.result, fmt::join(info.args, ", "), annotations, host_funcptr_entry.first); } + file << " { nullptr, nullptr }\n"; file << "};\n"; diff --git a/ThunkLibs/include/common/Host.h b/ThunkLibs/include/common/Host.h index f4fa32c41..bb4c9d69c 100644 --- a/ThunkLibs/include/common/Host.h +++ b/ThunkLibs/include/common/Host.h @@ -103,6 +103,9 @@ struct GuestcallInfo { asm volatile("mov %0, x11" : "=r" (target_variable)) #endif +struct ParameterAnnotations { +}; + template struct CallbackUnpack; @@ -120,8 +123,23 @@ struct CallbackUnpack { return packed_args.rv; } } +}; + +template +auto Projection(T& data) { + return data; +} + +template +struct GuestWrapperForHostFunction; + +template +struct GuestWrapperForHostFunction { + // Host functions called from Guest + template + static void Call(void* argsv) { + static_assert(sizeof...(Annotations) == sizeof...(Args)); - static void ForIndirectCall(void* argsv) { auto args = reinterpret_cast*>(argsv); constexpr auto CBIndex = sizeof...(Args); uintptr_t cb; @@ -168,8 +186,14 @@ struct CallbackUnpack { cb = args->a23; } + // This is almost the same type as "Result func(Args..., uintptr_t)", but + // individual parameters annotated as passthrough are replaced by guest_layout auto callback = reinterpret_cast(cb); - Invoke(callback, *args); + + auto f = [&callback](Args... args, uintptr_t target) -> Result { + return callback(Projection(args)..., target); + }; + Invoke(f, *args); } }; diff --git a/ThunkLibs/include/common/PackedArguments.h b/ThunkLibs/include/common/PackedArguments.h index 14531db2a..17af2f41c 100644 --- a/ThunkLibs/include/common/PackedArguments.h +++ b/ThunkLibs/include/common/PackedArguments.h @@ -113,8 +113,8 @@ T&& operator,(T&& t, Regularize) { return std::forward(t); } -template -void Invoke(Result(*func)(Args...), PackedArguments& args) { +template +void Invoke(Func&& func, PackedArguments& args) requires(std::is_invocable_r_v) { constexpr auto NumArgs = sizeof...(Args); static_assert(NumArgs <= 19 || NumArgs == 24); diff --git a/unittests/ThunkLibs/generator.cpp b/unittests/ThunkLibs/generator.cpp index 7144062ae..1677e81ef 100644 --- a/unittests/ThunkLibs/generator.cpp +++ b/unittests/ThunkLibs/generator.cpp @@ -270,9 +270,10 @@ SourceWithAST Fixture::run_thunkgen_host(std::string_view prelude, std::string_v " uintptr_t GuestUnpacker;\n" " uintptr_t GuestTarget;\n" "};\n" + "struct ParameterAnnotations {};\n" "template\n" - "struct CallbackUnpack {\n" - " static void ForIndirectCall(void* argsv);\n" + "struct GuestWrapperForHostFunction {\n" + " template static void Call(void*);\n" "};\n" "template\n" "void FinalizeHostTrampolineForGuestFunction(F*);\n" @@ -377,7 +378,7 @@ TEST_CASE_METHOD(Fixture, "FunctionPointerViaType") { matches(varDecl( hasName("exports"), hasType(constantArrayType(hasElementType(asString("struct ExportEntry")), hasSize(2))), - hasInitializer(hasDescendant(declRefExpr(to(cxxMethodDecl(hasName("ForIndirectCall"), ofClass(hasName("CallbackUnpack"))).bind("funcptr"))))) + hasInitializer(hasDescendant(declRefExpr(to(cxxMethodDecl(hasName("Call"), ofClass(hasName("GuestWrapperForHostFunction"))).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)"; @@ -415,7 +416,7 @@ TEST_CASE_METHOD(Fixture, "FunctionPointerParameter") { matches(varDecl( hasName("exports"), hasType(constantArrayType(hasElementType(asString("struct ExportEntry")), hasSize(3))), - hasInitializer(hasDescendant(declRefExpr(to(cxxMethodDecl(hasName("ForIndirectCall"), ofClass(hasName("CallbackUnpack"))))))) + hasInitializer(hasDescendant(declRefExpr(to(cxxMethodDecl(hasName("Call"), ofClass(hasName("GuestWrapperForHostFunction"))))))) ))); }