mirror of
https://github.com/FEX-Emu/FEX.git
synced 2026-10-08 19:00:18 +02:00
This runs the data layout analysis pass added in the previous change twice: Once for the host architecture and once for the guest architecture. This allows the new DataLayoutCompareAction to query architecture differences for each type, which can then be used to instruct code generation accordingly. Currently, type compatibility is classified into 3 categories: * Fully compatible (same size/alignment for the type itself and any members) * Repackable (incompatibility can be resolved with emission of automatable repacking code, e.g. when struct members are located at differing offsets due to padding bytes) * Incompatible
319 lines
16 KiB
C++
319 lines
16 KiB
C++
#include "analysis.h"
|
|
#include "data_layout.h"
|
|
#include "interface.h"
|
|
|
|
#include <fmt/format.h>
|
|
|
|
#include <openssl/sha.h>
|
|
|
|
constexpr bool enable_debug_output = false;
|
|
|
|
// Visitor for gathering data layout information that can be passed across libclang invocations
|
|
class AnalyzeDataLayoutAction : public AnalysisAction {
|
|
ABI& type_abi;
|
|
|
|
void OnAnalysisComplete(clang::ASTContext&) override;
|
|
|
|
public:
|
|
AnalyzeDataLayoutAction(ABI&);
|
|
};
|
|
|
|
AnalyzeDataLayoutAction::AnalyzeDataLayoutAction(ABI& abi_) : type_abi(abi_) {
|
|
}
|
|
|
|
std::unordered_map<const clang::Type*, TypeInfo> ComputeDataLayout(const clang::ASTContext& context, const std::unordered_map<const clang::Type*, AnalysisAction::RepackedType>& types) {
|
|
std::unordered_map<const clang::Type*, TypeInfo> layout;
|
|
|
|
// First, add all types directly used in function signatures of the library API to the meta set
|
|
for (const auto& [type, type_repack_info] : types) {
|
|
if (type->isIncompleteType()) {
|
|
throw std::runtime_error("Cannot compute data layout of incomplete type \"" + clang::QualType { type, 0 }.getAsString() + "\". Did you forget any annotations?");
|
|
}
|
|
|
|
if (type->isStructureType()) {
|
|
StructInfo info;
|
|
info.size_bits = context.getTypeSize(type);
|
|
info.alignment_bits = context.getTypeAlign(type);
|
|
|
|
auto [_, inserted] = layout.insert(std::pair { context.getCanonicalType(type), info });
|
|
if (!inserted) {
|
|
throw std::runtime_error("Failed to gather type metadata: Type \"" + clang::QualType { type, 0 }.getAsString() + "\" already registered");
|
|
}
|
|
} else if (type->isBuiltinType() || type->isEnumeralType()) {
|
|
SimpleTypeInfo info;
|
|
info.size_bits = context.getTypeSize(type);
|
|
info.alignment_bits = context.getTypeAlign(type);
|
|
|
|
// NOTE: Non-enum types are intentionally not canonicalized since that would turn e.g. size_t into platform-specific types
|
|
auto [_, inserted] = layout.insert(std::pair { type->isEnumeralType() ? context.getCanonicalType(type) : type, info });
|
|
if (!inserted) {
|
|
throw std::runtime_error("Failed to gather type metadata: Type \"" + clang::QualType { type, 0 }.getAsString() + "\" already registered");
|
|
}
|
|
}
|
|
}
|
|
|
|
// Then, add information about members
|
|
for (const auto& [type, type_repack_info] : types) {
|
|
if (!type->isStructureType()) {
|
|
continue;
|
|
}
|
|
|
|
auto& info = *layout.at(context.getCanonicalType(type)).get_if_struct();
|
|
|
|
for (auto* field : type->getAsStructureType()->getDecl()->fields()) {
|
|
auto field_type = field->getType().getTypePtr();
|
|
std::optional<uint64_t> array_size;
|
|
if (auto array_type = llvm::dyn_cast<clang::ConstantArrayType>(field->getType())) {
|
|
array_size = array_type->getSize().getZExtValue();
|
|
field_type = array_type->getElementType().getTypePtr();
|
|
if (llvm::isa<clang::ConstantArrayType>(field_type)) {
|
|
throw std::runtime_error("Unsupported multi-dimensional array member \"" + field->getNameAsString() + "\" in type \"" + clang::QualType { type, 0 }.getAsString() + "\"");
|
|
}
|
|
}
|
|
|
|
StructInfo::MemberInfo member_info {
|
|
.size_bits = context.getTypeSize(field->getType()), // Total size even for arrays
|
|
.offset_bits = context.getFieldOffset(field),
|
|
.type_name = get_type_name(context, field_type),
|
|
.member_name = field->getNameAsString(),
|
|
.array_size = array_size,
|
|
};
|
|
|
|
// TODO: Process types in dependency-order. Currently we skip this
|
|
// check if we haven't processed the member type already,
|
|
// which is only safe since this is a consistency check
|
|
if (field_type->isStructureType() && layout.contains(context.getCanonicalType(field_type))) {
|
|
// Assert for self-consistency
|
|
auto field_meta = layout.at(context.getCanonicalType(field_type));
|
|
(void)types.at(context.getCanonicalType(field_type));
|
|
if (auto field_info = field_meta.get_if_simple_or_struct()) {
|
|
if (field_info->size_bits != member_info.size_bits / member_info.array_size.value_or(1)) {
|
|
throw std::runtime_error("Inconsistent type size detected");
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add built-in types, even if referenced through a pointer
|
|
for (auto* inner_field_type = field_type; inner_field_type; inner_field_type = inner_field_type->getPointeeType().getTypePtrOrNull()) {
|
|
if (inner_field_type->isBuiltinType() || inner_field_type->isEnumeralType()) {
|
|
// The analysis pass doesn't explicitly register built-in types, so add them manually here
|
|
SimpleTypeInfo info {
|
|
.size_bits = context.getTypeSize(inner_field_type),
|
|
.alignment_bits = context.getTypeAlign(inner_field_type),
|
|
};
|
|
if (!inner_field_type->isBuiltinType()) {
|
|
inner_field_type = context.getCanonicalType(inner_field_type);
|
|
}
|
|
[[maybe_unused]] auto [prev, inserted] = layout.insert(std::pair { inner_field_type, info });
|
|
// if (!inserted && prev->second != TypeInfo { info }) {
|
|
// // TODO: Throw error since consistency check failed
|
|
// }
|
|
}
|
|
}
|
|
|
|
info.members.push_back(member_info);
|
|
}
|
|
}
|
|
|
|
if (enable_debug_output) {
|
|
for (const auto& [type, info] : layout) {
|
|
auto basic_info = info.get_if_simple_or_struct();
|
|
if (!basic_info) {
|
|
continue;
|
|
}
|
|
|
|
fprintf(stderr, " Host entry %s: %lu (%lu)\n", clang::QualType { type, 0 }.getAsString().c_str(), basic_info->size_bits / 8, basic_info->alignment_bits / 8);
|
|
|
|
if (auto struct_info = info.get_if_struct()) {
|
|
for (const auto& member : struct_info->members) {
|
|
fprintf(stderr, " Offset %lu-%lu: %s %s%s\n", member.offset_bits / 8, (member.offset_bits + member.size_bits - 1) / 8, member.type_name.c_str(), member.member_name.c_str(), member.array_size ? fmt::format("[{}]", member.array_size.value()).c_str() : "");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return layout;
|
|
}
|
|
|
|
ABI GetStableLayout(const clang::ASTContext& context, const std::unordered_map<const clang::Type*, TypeInfo>& data_layout) {
|
|
ABI stable_layout;
|
|
|
|
for (auto [type, type_info] : data_layout) {
|
|
auto type_name = get_type_name(context, type);
|
|
auto [it, inserted] = stable_layout.insert(std::pair { type_name, type_info });
|
|
if (!inserted && it->second != type_info) {
|
|
throw std::runtime_error("Duplicate type information: Tried to re-register type \"" + type_name + "\"");
|
|
}
|
|
}
|
|
|
|
stable_layout.pointer_size = context.getTypeSize(context.getUIntPtrType()) / 8;
|
|
|
|
return stable_layout;
|
|
}
|
|
|
|
void AnalyzeDataLayoutAction::OnAnalysisComplete(clang::ASTContext& context) {
|
|
if (StrictModeEnabled(context)) {
|
|
type_abi = GetStableLayout(context, ComputeDataLayout(context, types));
|
|
}
|
|
}
|
|
|
|
TypeCompatibility DataLayoutCompareAction::GetTypeCompatibility(
|
|
const clang::ASTContext& context,
|
|
const clang::Type* type,
|
|
const std::unordered_map<const clang::Type*, TypeInfo> host_abi,
|
|
std::unordered_map<const clang::Type*, TypeCompatibility>& type_compat) {
|
|
assert(type->isCanonicalUnqualified() || type->isBuiltinType() || type->isEnumeralType());
|
|
|
|
{
|
|
// Reserve a slot to be filled later. The placeholder value is used
|
|
// to detect infinite recursions.
|
|
constexpr auto placeholder_compat = TypeCompatibility { 100 };
|
|
auto [existing_compat_it, is_new_type] = type_compat.emplace(type, placeholder_compat);
|
|
if (!is_new_type) {
|
|
if (existing_compat_it->second == placeholder_compat) {
|
|
throw std::runtime_error("Found recursive reference to type \"" + clang::QualType { type, 0 }.getAsString() + "\"");
|
|
}
|
|
|
|
return existing_compat_it->second;
|
|
}
|
|
}
|
|
|
|
auto type_name = get_type_name(context, type);
|
|
auto& guest_info = guest_abi.at(type_name);
|
|
auto& host_info = host_abi.at(type->isBuiltinType() ? type : context.getCanonicalType(type));
|
|
|
|
const bool is_32bit = (guest_abi.pointer_size == 4);
|
|
|
|
// Assume full compatibility, then downgrade as needed
|
|
auto compat = TypeCompatibility::Full;
|
|
|
|
if (guest_info != host_info) {
|
|
// Non-matching data layout... downgrade to Repackable
|
|
// TODO: Even for non-structs, this only works if the types are reasonably similar (e.g. uint32_t -> uint64_t)
|
|
compat = TypeCompatibility::Repackable;
|
|
}
|
|
|
|
auto guest_struct_info = guest_info.get_if_struct();
|
|
if (guest_struct_info && guest_struct_info->members.size() != host_info.get_if_struct()->members.size()) {
|
|
// Members are missing from either the guest or host layout
|
|
// NOTE: If the members are merely named differently, this will be caught in the else-if below
|
|
compat = TypeCompatibility::None;
|
|
} else if (guest_struct_info) {
|
|
std::vector<TypeCompatibility> member_compat;
|
|
for (std::size_t member_idx = 0; member_idx < guest_struct_info->members.size(); ++member_idx) {
|
|
// Look up the corresponding member in the host struct definition.
|
|
// The members may be listed in a different order, so we can't
|
|
// directly use member_idx for this
|
|
auto* host_member_field = [&]() -> clang::FieldDecl* {
|
|
auto struct_decl = type->getAsStructureType()->getDecl();
|
|
auto it = std::find_if(struct_decl->field_begin(), struct_decl->field_end(), [&](auto* field) {
|
|
return field->getName() == guest_struct_info->members.at(member_idx).member_name;
|
|
});
|
|
if (it == struct_decl->field_end()) {
|
|
return nullptr;
|
|
}
|
|
return *it;
|
|
}();
|
|
if (!host_member_field) {
|
|
// No corresponding host struct member
|
|
// TODO: Also detect host members that are missing from the guest struct
|
|
member_compat.push_back(TypeCompatibility::None);
|
|
break;
|
|
}
|
|
|
|
auto host_member_type = context.getCanonicalType(host_member_field->getType().getTypePtr());
|
|
if (auto array_type = llvm::dyn_cast<clang::ConstantArrayType>(host_member_type)) {
|
|
// Compare array element type only. The array size is already considered by the layout information of the containing struct.
|
|
host_member_type = context.getCanonicalType(array_type->getElementType().getTypePtr());
|
|
}
|
|
|
|
if (host_member_type->isPointerType()) {
|
|
// Automatic repacking of pointers to non-compatible types is only possible if:
|
|
// * Pointee is fully compatible, or
|
|
// * Pointer member is annotated
|
|
// TODO: Don't restrict this to structure types. it applies to pointers to builtin types too!
|
|
auto host_member_pointee_type = context.getCanonicalType(host_member_type->getPointeeType().getTypePtr());
|
|
if (host_member_pointee_type->isPointerType()) {
|
|
// This is a nested pointer, e.g. void**
|
|
|
|
if (is_32bit) {
|
|
// Nested pointers can't be repacked on 32-bit
|
|
member_compat.push_back(TypeCompatibility::None);
|
|
} else {
|
|
// Check the innermost type's compatibility on 64-bit
|
|
auto pointee_pointee_type = host_member_pointee_type->getPointeeType().getTypePtr();
|
|
// TODO: Not sure how to handle void here. Probably should require an annotation instead of "just working"
|
|
auto pointee_pointee_compat = pointee_pointee_type->isVoidType() ? TypeCompatibility::Full : GetTypeCompatibility(context, pointee_pointee_type, host_abi, type_compat);
|
|
if (pointee_pointee_compat == TypeCompatibility::Full) {
|
|
member_compat.push_back(TypeCompatibility::Full);
|
|
} else {
|
|
member_compat.push_back(TypeCompatibility::None);
|
|
}
|
|
}
|
|
} else if (!host_member_pointee_type->isVoidType() && (host_member_pointee_type->isBuiltinType() || host_member_pointee_type->isEnumeralType())) {
|
|
// TODO: What are good heuristics for this?
|
|
// size_t should yield TypeCompatibility::Repackable
|
|
// inconsistent types should probably default to TypeCompatibility::None
|
|
// For now, just always assume compatible... (will degrade to Repackable below)
|
|
member_compat.push_back(TypeCompatibility::Full);
|
|
} else if (!host_member_pointee_type->isVoidType() && (host_member_pointee_type->isStructureType() || types.contains(host_member_pointee_type))) {
|
|
auto pointee_compat = GetTypeCompatibility(context, host_member_pointee_type, host_abi, type_compat);
|
|
if (pointee_compat == TypeCompatibility::Full) {
|
|
// Pointee is fully compatible, so automatic repacking only requires converting the pointers themselves
|
|
member_compat.push_back(is_32bit ? TypeCompatibility::Repackable : TypeCompatibility::Full);
|
|
} else {
|
|
// If the pointee is incompatible (even if repackable), automatic repacking isn't possible
|
|
member_compat.push_back(TypeCompatibility::None);
|
|
}
|
|
} else if (!is_32bit && host_member_pointee_type->isVoidType()) {
|
|
// TODO: Not sure how to handle void here. Probably should require an annotation instead of "just working"
|
|
member_compat.push_back(TypeCompatibility::Full);
|
|
} else {
|
|
member_compat.push_back(TypeCompatibility::None);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (guest_abi.at(guest_struct_info->members[member_idx].type_name).get_if_struct()) {
|
|
auto host_type_info = host_abi.at(host_member_type);
|
|
member_compat.push_back(GetTypeCompatibility(context, host_member_type, host_abi, type_compat));
|
|
} else {
|
|
// Member was checked for size/alignment above already
|
|
}
|
|
}
|
|
|
|
if (std::all_of(member_compat.begin(), member_compat.end(), [](auto compat) { return compat == TypeCompatibility::Full; })) {
|
|
// TypeCompatibility::Full or ::Repackable
|
|
} else if (std::none_of(member_compat.begin(), member_compat.end(), [](auto compat) { return compat == TypeCompatibility::None; })) {
|
|
// Downgrade to Repackable
|
|
compat = TypeCompatibility::Repackable;
|
|
} else {
|
|
// Downgrade to None
|
|
compat = TypeCompatibility::None;
|
|
}
|
|
}
|
|
|
|
type_compat.at(type) = compat;
|
|
return compat;
|
|
}
|
|
|
|
DataLayoutCompareActionFactory::DataLayoutCompareActionFactory(const ABI& abi) : abi(abi) {
|
|
|
|
}
|
|
|
|
DataLayoutCompareActionFactory::~DataLayoutCompareActionFactory() = default;
|
|
|
|
std::unique_ptr<clang::FrontendAction> DataLayoutCompareActionFactory::create() {
|
|
return std::make_unique<DataLayoutCompareAction>(abi);
|
|
}
|
|
|
|
AnalyzeDataLayoutActionFactory::AnalyzeDataLayoutActionFactory() : abi(std::make_unique<ABI>()) {
|
|
|
|
}
|
|
|
|
AnalyzeDataLayoutActionFactory::~AnalyzeDataLayoutActionFactory() = default;
|
|
|
|
std::unique_ptr<clang::FrontendAction> AnalyzeDataLayoutActionFactory::create() {
|
|
return std::make_unique<AnalyzeDataLayoutAction>(*abi);
|
|
}
|