mirror of
https://github.com/FEX-Emu/FEX.git
synced 2026-10-06 17:00:19 +02:00
The guest_layout wrapper provides an architecture-agnostic representation of the guest data layout of each struct used in a thunked library. A constructor is added to host_layout to allow conversion of the data to the host layout. For types that are already fully compatible, both layout wrappers are simple type aliases to minimize overhead.
192 lines
7.0 KiB
C++
192 lines
7.0 KiB
C++
#pragma once
|
|
|
|
#include <clang/Basic/FileEntry.h>
|
|
#include <clang/Frontend/FrontendAction.h>
|
|
|
|
#include <memory>
|
|
#include <optional>
|
|
#include <string>
|
|
#include <unordered_map>
|
|
#include <unordered_set>
|
|
#include <vector>
|
|
|
|
struct FunctionParams {
|
|
std::vector<clang::QualType> param_types;
|
|
};
|
|
|
|
struct ThunkedCallback : FunctionParams {
|
|
clang::QualType return_type;
|
|
|
|
bool is_stub = false; // Callback will be replaced by a stub that calls std::abort
|
|
bool is_variadic = false;
|
|
};
|
|
|
|
struct ParameterAnnotations {
|
|
bool is_passthrough = false;
|
|
bool assume_compatible = false;
|
|
|
|
bool operator==(const ParameterAnnotations&) const = default;
|
|
};
|
|
|
|
/**
|
|
* Guest<->Host transition point.
|
|
*
|
|
* These are normally used to translate the public API of the guest to host
|
|
* function calls (ThunkedAPIFunction), but a thunk library may also define
|
|
* internal thunks that don't correspond to any function in the implemented
|
|
* API.
|
|
*/
|
|
struct ThunkedFunction : FunctionParams {
|
|
std::string function_name;
|
|
clang::QualType return_type;
|
|
|
|
// If true, param_types contains an extra size_t and the valist for marshalling through an internal function
|
|
bool is_variadic = false;
|
|
|
|
// If true, the unpacking function will call a custom fexfn_impl function
|
|
// to be provided manually instead of calling the host library function
|
|
// directly.
|
|
// This is implied e.g. for thunks generated for variadic functions
|
|
bool custom_host_impl = false;
|
|
|
|
std::string GetOriginalFunctionName() const {
|
|
const std::string suffix = "_internal";
|
|
assert(function_name.length() > suffix.size());
|
|
assert((std::string_view { &*function_name.end() - suffix.size(), suffix.size() } == suffix));
|
|
return function_name.substr(0, function_name.size() - suffix.size());
|
|
}
|
|
|
|
// Maps parameter index to ThunkedCallback
|
|
std::unordered_map<unsigned, ThunkedCallback> callbacks;
|
|
|
|
// Maps parameter index to ParameterAnnotations
|
|
// TODO: Use index -1 for the return value?
|
|
std::unordered_map<unsigned, ParameterAnnotations> param_annotations;
|
|
|
|
clang::FunctionDecl* decl;
|
|
};
|
|
|
|
/**
|
|
* Function that is part of the API of the thunked library.
|
|
*
|
|
* For each of these, there is:
|
|
* - A publicly visible guest entrypoint (usually auto-generated but may be manually defined)
|
|
* - A pointer to the native host library function loaded through dlsym (or a user-provided function specified via host_loader)
|
|
* - A ThunkedFunction with the same function_name (possibly suffixed with _internal)
|
|
*/
|
|
struct ThunkedAPIFunction : FunctionParams {
|
|
std::string function_name;
|
|
|
|
clang::QualType return_type;
|
|
|
|
// name of the function to load the native host symbol with
|
|
std::string host_loader;
|
|
|
|
// If true, no guest-side implementation of this function will be autogenerated
|
|
bool custom_guest_impl;
|
|
|
|
bool is_variadic;
|
|
|
|
// Index of the symbol table to store this export in (see guest_symtables).
|
|
// If empty, a library export is created, otherwise the function is entered into a function pointer array
|
|
std::optional<std::size_t> symtable_namespace;
|
|
};
|
|
|
|
struct NamespaceInfo {
|
|
clang::DeclContext* context;
|
|
|
|
std::string name;
|
|
|
|
// Function to load native host library functions with.
|
|
// This function must be defined manually with the signature "void* func(void*, const char*)"
|
|
std::string host_loader;
|
|
|
|
bool generate_guest_symtable;
|
|
|
|
bool indirect_guest_calls;
|
|
};
|
|
|
|
class AnalysisAction : public clang::ASTFrontendAction {
|
|
public:
|
|
AnalysisAction() {
|
|
decl_contexts.push_back(nullptr); // global namespace (replaced by getTranslationUnitDecl later)
|
|
}
|
|
|
|
void ExecuteAction() override;
|
|
|
|
std::unique_ptr<clang::ASTConsumer> CreateASTConsumer(clang::CompilerInstance&, clang::StringRef /*file*/) override;
|
|
|
|
struct RepackedType {
|
|
bool assumed_compatible = false; // opaque_type or assume_compatible_data_layout
|
|
bool pointers_only = assumed_compatible; // if true, only pointers to this type may be used
|
|
|
|
// If true, emit guest_layout/host_layout definitions even if the type is non-repackable
|
|
bool emit_layout_wrappers = false;
|
|
};
|
|
|
|
protected:
|
|
// Build the internal API representation by processing fex_gen_config and other annotated entities
|
|
void ParseInterface(clang::ASTContext&);
|
|
|
|
// Recursively extend the type set to include types of struct members
|
|
void CoverReferencedTypes(clang::ASTContext&);
|
|
|
|
// Called from ExecuteAction() after parsing is complete
|
|
virtual void OnAnalysisComplete(clang::ASTContext&) {};
|
|
|
|
std::vector<clang::DeclContext*> decl_contexts;
|
|
|
|
std::vector<ThunkedFunction> thunks;
|
|
std::vector<ThunkedAPIFunction> thunked_api;
|
|
|
|
// Set of function types for which to generate Guest->Host thunking trampolines.
|
|
// 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<std::string, std::pair<const clang::Type*, std::unordered_map<unsigned, ParameterAnnotations>>> thunked_funcptrs;
|
|
|
|
std::unordered_map<const clang::Type*, RepackedType> types;
|
|
std::optional<unsigned> lib_version;
|
|
std::vector<NamespaceInfo> namespaces;
|
|
|
|
RepackedType& LookupType(clang::ASTContext& context, const clang::Type* type) {
|
|
return types.at(context.getCanonicalType(type));
|
|
}
|
|
};
|
|
|
|
inline std::string get_type_name(const clang::ASTContext& context, const clang::Type* type) {
|
|
if (type->isBuiltinType()) {
|
|
// Skip canonicalization
|
|
return clang::QualType { type, 0 }.getAsString();
|
|
}
|
|
|
|
if (auto decl = type->getAsTagDecl()) {
|
|
// Replace unnamed types with a placeholder. This will fail to compile if referenced
|
|
// anywhere in generated code, but at least it will point to a useful location.
|
|
//
|
|
// A notable exception are C-style struct declarations like "typedef struct (unnamed) { ... } MyStruct;".
|
|
// A typedef name is associated with these for linking purposes, so
|
|
// getAsString() will produce a usable identifier.
|
|
// TODO: Consider turning this into a hard error instead of replacing the name
|
|
if (!decl->getDeclName() && !decl->getTypedefNameForAnonDecl()) {
|
|
auto loc = context.getSourceManager().getPresumedLoc(decl->getLocation());
|
|
std::string filename = loc.getFilename();
|
|
filename = std::move(filename).substr(filename.rfind("/"));
|
|
filename = std::move(filename).substr(1);
|
|
std::replace(filename.begin(), filename.end(), '.', '_');
|
|
return "unnamed_type_" + filename + "_" + std::to_string(loc.getLine());
|
|
}
|
|
}
|
|
|
|
auto type_name = clang::QualType { context.getCanonicalType(type), 0 }.getAsString();
|
|
if (type_name.starts_with("struct ")) {
|
|
type_name = type_name.substr(7);
|
|
}
|
|
if (type_name.starts_with("class ") || type_name.starts_with("union ")) {
|
|
type_name = type_name.substr(6);
|
|
}
|
|
if (type_name.starts_with("enum ")) {
|
|
type_name = type_name.substr(5);
|
|
}
|
|
return type_name;
|
|
}
|