mirror of
https://github.com/FEX-Emu/FEX.git
synced 2026-10-06 14:00:16 +02:00
unittests/ThunkLibs: Add data layout tests
This commit is contained in:
1 parent
2b472cb962
commit
b04b0549a9
3 files changed
+684
-4
No files matched your search
@@ -1,5 +1,6 @@
|
||||
add_executable(thunkgentest generator.cpp)
|
||||
add_executable(thunkgentest generator.cpp abi.cpp)
|
||||
target_link_libraries(thunkgentest PRIVATE Catch2::Catch2WithMain)
|
||||
target_link_libraries(thunkgentest PRIVATE fmt::fmt)
|
||||
target_link_libraries(thunkgentest PRIVATE thunkgenlib)
|
||||
catch_discover_tests(thunkgentest TEST_SUFFIX ".ThunkGen")
|
||||
|
||||
|
||||
@@ -0,0 +1,643 @@
|
||||
#include <clang/Frontend/CompilerInstance.h>
|
||||
#include <catch2/catch.hpp>
|
||||
|
||||
#include <data_layout.h>
|
||||
#include <interface.h>
|
||||
#include "common.h"
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include <string_view>
|
||||
|
||||
// run_tool will leak memory when the ToolAction throws an exception, so
|
||||
// disable AddressSanitizer's leak detection
|
||||
const char* __asan_default_options() {
|
||||
return "detect_leaks=0";
|
||||
}
|
||||
|
||||
inline std::ostream& operator<<(std::ostream& os, TypeCompatibility compat) {
|
||||
if (compat == TypeCompatibility::Full) {
|
||||
os << "Compatible";
|
||||
} else if (compat == TypeCompatibility::Repackable) {
|
||||
os << "Repackable";
|
||||
} else if (compat == TypeCompatibility::None) {
|
||||
os << "Incompatible";
|
||||
} else {
|
||||
os << "(INVALID)";
|
||||
}
|
||||
return os;
|
||||
}
|
||||
|
||||
class DataLayoutCompareActionForTest;
|
||||
|
||||
namespace {
|
||||
|
||||
struct Fixture {
|
||||
/**
|
||||
* Parses annotations from the input source and generates data layout descriptions from it.
|
||||
*
|
||||
* Input code with common definitions (types, functions, ...) should be specified in "prelude".
|
||||
* It will be prepended to "code" before processing and also to the generator output.
|
||||
*/
|
||||
std::unique_ptr<DataLayoutCompareActionForTest> compute_data_layout(std::string_view prelude, std::string_view code, GuestABI);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
class DataLayoutCompareActionForTest : public DataLayoutCompareAction {
|
||||
std::unordered_map<const clang::Type*, TypeCompatibility> type_compat_cache;
|
||||
|
||||
// Persistent reference taken to enable accessing the ASTContext after CompilerInstance::ExecuteAction returns
|
||||
llvm::IntrusiveRefCntPtr<clang::ASTContext> ast_context;
|
||||
std::shared_ptr<clang::Preprocessor> preprocessor;
|
||||
|
||||
public:
|
||||
DataLayoutCompareActionForTest(std::unique_ptr<ABI> guest_layout) : DataLayoutCompareAction(*guest_layout), guest_layout(std::move(guest_layout)) {
|
||||
}
|
||||
|
||||
void ExecuteAction() override {
|
||||
AnalysisAction::ExecuteAction();
|
||||
|
||||
ast_context = &getCompilerInstance().getASTContext();
|
||||
preprocessor = getCompilerInstance().getPreprocessorPtr();
|
||||
host_layout = ComputeDataLayout(*ast_context, types);
|
||||
}
|
||||
|
||||
std::unique_ptr<ABI> guest_layout;
|
||||
std::unordered_map<const clang::Type*, TypeInfo> host_layout;
|
||||
|
||||
TypeCompatibility GetTypeCompatibility(std::string_view type_name) {
|
||||
for (const auto& [type, _] : host_layout) {
|
||||
if (clang::QualType { type, 0 }.getAsString() == type_name) {
|
||||
return DataLayoutCompareAction::GetTypeCompatibility(*ast_context, type, host_layout, type_compat_cache);
|
||||
}
|
||||
}
|
||||
|
||||
throw std::runtime_error("No data layout information recorded for type \"" + std::string { type_name } + "\"");
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Same as clang::FrontendActionFactory but takes an external FrontendAction
|
||||
* reference instead of constructing an internal one. Since the FrontendAction
|
||||
* lifetime may extend past this ToolAction, state captured by the
|
||||
* FrontendAction can be accessed after the ToolAction returns.
|
||||
*/
|
||||
class ThunkTestToolAction : public clang::tooling::ToolAction {
|
||||
public:
|
||||
clang::FrontendAction& ScopedToolAction;
|
||||
|
||||
public:
|
||||
ThunkTestToolAction(clang::FrontendAction& action) : ScopedToolAction(action) {
|
||||
}
|
||||
~ThunkTestToolAction() = default;
|
||||
|
||||
// Same as FrontendActionFactory but keeps ScopedToolAction alive when returning
|
||||
bool runInvocation( std::shared_ptr<clang::CompilerInvocation> invocation, clang::FileManager *files,
|
||||
std::shared_ptr<clang::PCHContainerOperations> pch,
|
||||
clang::DiagnosticConsumer *diag_consumer) override {
|
||||
|
||||
auto diagnostics = clang::CompilerInstance::createDiagnostics(&invocation->getDiagnosticOpts(), diag_consumer, false);
|
||||
|
||||
clang::CompilerInstance Compiler(std::move(pch));
|
||||
Compiler.setInvocation(std::move(invocation));
|
||||
Compiler.setFileManager(files);
|
||||
Compiler.createDiagnostics(diag_consumer, false);
|
||||
if (!Compiler.hasDiagnostics())
|
||||
return false;
|
||||
Compiler.createSourceManager(*files);
|
||||
|
||||
const bool Success = Compiler.ExecuteAction(ScopedToolAction);
|
||||
|
||||
files->clearStatCache();
|
||||
return Success;
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<DataLayoutCompareActionForTest> Fixture::compute_data_layout(std::string_view prelude, std::string_view code, GuestABI guest_abi) {
|
||||
const std::string full_code = std::string { prelude } + std::string { code };
|
||||
|
||||
// Compute guest data layout
|
||||
auto data_layout_analysis_factory = std::make_unique<AnalyzeDataLayoutActionFactory>();
|
||||
run_tool(*data_layout_analysis_factory, full_code, false, guest_abi);
|
||||
|
||||
// Compute host data layout
|
||||
auto ScopedToolAction = std::make_unique<DataLayoutCompareActionForTest>(data_layout_analysis_factory->TakeDataLayout());
|
||||
run_tool(std::make_unique<ThunkTestToolAction>(*ScopedToolAction), full_code, false, std::nullopt);
|
||||
|
||||
return ScopedToolAction;
|
||||
}
|
||||
|
||||
static std::string FormatDataLayout(const std::unordered_map<const clang::Type*, TypeInfo>& layout) {
|
||||
std::string ret;
|
||||
|
||||
for (const auto& [type, info] : layout) {
|
||||
auto basic_info = info.get_if_simple_or_struct();
|
||||
if (!basic_info) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ret += fmt::format(" Host entry {}: {} ({})\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) {
|
||||
ret += fmt::format(" Offset {}-{}: {} {}{}\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 ret;
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(Fixture, "DataLayout") {
|
||||
auto guest_abi = GENERATE(GuestABI::X86_32, GuestABI::X86_64);
|
||||
INFO(guest_abi);
|
||||
|
||||
SECTION("Trivial") {
|
||||
auto action = compute_data_layout(
|
||||
"#include <thunks_common.h>\n",
|
||||
"struct A { int a; };\n"
|
||||
"template<> struct fex_gen_type<A> {};\n", guest_abi);
|
||||
|
||||
REQUIRE(action->guest_layout->contains("A"));
|
||||
|
||||
CHECK(action->GetTypeCompatibility("struct A") == TypeCompatibility::Full);
|
||||
}
|
||||
|
||||
SECTION("Builtin types") {
|
||||
auto action = compute_data_layout(
|
||||
"#include <thunks_common.h>\n",
|
||||
"struct A { char a; short b; int c; float d; };\n"
|
||||
"template<> struct fex_gen_type<A> {};\n", guest_abi);
|
||||
|
||||
REQUIRE(action->guest_layout->contains("A"));
|
||||
|
||||
CHECK(action->GetTypeCompatibility("struct A") == TypeCompatibility::Full);
|
||||
CHECK(action->GetTypeCompatibility("char") == TypeCompatibility::Full);
|
||||
CHECK(action->GetTypeCompatibility("short") == TypeCompatibility::Full);
|
||||
CHECK(action->GetTypeCompatibility("int") == TypeCompatibility::Full);
|
||||
CHECK(action->GetTypeCompatibility("float") == TypeCompatibility::Full);
|
||||
}
|
||||
|
||||
SECTION("Padding after int16_t") {
|
||||
auto action = compute_data_layout(
|
||||
"#include <thunks_common.h>\n"
|
||||
"#include <cstdint>\n",
|
||||
"struct A { int16_t a; int32_t b; };\n"
|
||||
"template<> struct fex_gen_type<A> {};\n", guest_abi);
|
||||
|
||||
INFO(FormatDataLayout(action->host_layout));
|
||||
|
||||
REQUIRE(action->guest_layout->contains("A"));
|
||||
CHECK(action->GetTypeCompatibility("struct A") == TypeCompatibility::Full);
|
||||
}
|
||||
|
||||
SECTION("Array of int16_t") {
|
||||
auto action = compute_data_layout(
|
||||
"#include <thunks_common.h>\n"
|
||||
"#include <cstdint>\n",
|
||||
"struct A { int16_t a[64]; };\n"
|
||||
"template<> struct fex_gen_type<A> {};\n", guest_abi);
|
||||
|
||||
INFO(FormatDataLayout(action->host_layout));
|
||||
|
||||
REQUIRE(action->guest_layout->contains("A"));
|
||||
CHECK(action->GetTypeCompatibility("struct A") == TypeCompatibility::Full);
|
||||
}
|
||||
|
||||
const auto compat_full64_repackable32 = (guest_abi == GuestABI::X86_32 ? TypeCompatibility::Repackable : TypeCompatibility::Full);
|
||||
|
||||
SECTION("Type with platform-dependent size (size_t)") {
|
||||
auto action = compute_data_layout(
|
||||
"#include <thunks_common.h>\n"
|
||||
"#include <cstdlib>\n",
|
||||
"struct A { size_t a; };\n"
|
||||
"template<> struct fex_gen_type<A> {};\n", guest_abi);
|
||||
|
||||
INFO(FormatDataLayout(action->host_layout));
|
||||
|
||||
REQUIRE(action->guest_layout->contains("A"));
|
||||
CHECK(action->GetTypeCompatibility("struct A") == compat_full64_repackable32);
|
||||
}
|
||||
|
||||
SECTION("int64_t has stricter alignment requirements on 64-bit platforms") {
|
||||
auto action = compute_data_layout(
|
||||
"#include <thunks_common.h>\n"
|
||||
"#include <cstdint>\n",
|
||||
"struct A { int64_t a; };\n"
|
||||
"template<> struct fex_gen_type<A> {};\n", guest_abi);
|
||||
|
||||
INFO(FormatDataLayout(action->host_layout));
|
||||
|
||||
REQUIRE(action->guest_layout->contains("A"));
|
||||
CHECK(action->guest_layout->at("A").get_if_struct()->alignment_bits == (guest_abi == GuestABI::X86_32 ? 32 : 64));
|
||||
CHECK(action->GetTypeCompatibility("struct A") == compat_full64_repackable32);
|
||||
}
|
||||
|
||||
SECTION("Array of int64_t") {
|
||||
auto action = compute_data_layout(
|
||||
"#include <thunks_common.h>\n"
|
||||
"#include <cstdint>\n",
|
||||
"struct A { int64_t a[64]; };\n"
|
||||
"template<> struct fex_gen_type<A> {};\n", guest_abi);
|
||||
|
||||
INFO(FormatDataLayout(action->host_layout));
|
||||
|
||||
REQUIRE(action->guest_layout->contains("A"));
|
||||
CHECK(action->GetTypeCompatibility("struct A") == compat_full64_repackable32);
|
||||
}
|
||||
|
||||
SECTION("int64_t with explicit alignment specification") {
|
||||
auto action = compute_data_layout(
|
||||
"#include <thunks_common.h>\n"
|
||||
"#include <cstdint>\n",
|
||||
"struct alignas(8) A { int64_t a; };\n"
|
||||
"template<> struct fex_gen_type<A> {};\n", guest_abi);
|
||||
|
||||
INFO(FormatDataLayout(action->host_layout));
|
||||
|
||||
REQUIRE(action->guest_layout->contains("A"));
|
||||
CHECK(action->guest_layout->at("A").get_if_struct()->alignment_bits == 64);
|
||||
CHECK(action->GetTypeCompatibility("struct A") == TypeCompatibility::Full);
|
||||
}
|
||||
|
||||
SECTION("int64_t alignment requirements propagate to parent struct") {
|
||||
auto action = compute_data_layout(
|
||||
"#include <thunks_common.h>\n"
|
||||
"#include <cstdint>\n",
|
||||
"struct A { int32_t a; int32_t b; int64_t c; };\n"
|
||||
"template<> struct fex_gen_type<A> {};\n", guest_abi);
|
||||
|
||||
INFO(FormatDataLayout(action->host_layout));
|
||||
|
||||
REQUIRE(action->guest_layout->contains("A"));
|
||||
CHECK(action->guest_layout->at("A").get_if_struct()->alignment_bits == (guest_abi == GuestABI::X86_32 ? 32 : 64));
|
||||
CHECK(action->GetTypeCompatibility("struct A") == compat_full64_repackable32);
|
||||
}
|
||||
|
||||
SECTION("Padding before int64_t member") {
|
||||
auto action = compute_data_layout(
|
||||
"#include <thunks_common.h>\n"
|
||||
"#include <cstdint>\n",
|
||||
"struct A { int32_t a; int64_t b; };\n"
|
||||
"template<> struct fex_gen_type<A> {};\n", guest_abi);
|
||||
|
||||
INFO(FormatDataLayout(action->host_layout));
|
||||
|
||||
REQUIRE(action->guest_layout->contains("A"));
|
||||
CHECK(action->guest_layout->at("A").get_if_struct()->members[1].offset_bits == (guest_abi == GuestABI::X86_32 ? 32 : 64));
|
||||
|
||||
CHECK(action->GetTypeCompatibility("struct A") == compat_full64_repackable32);
|
||||
}
|
||||
|
||||
SECTION("Padding at end of struct due to int64_t alignment (like VkMemoryHeap)") {
|
||||
auto action = compute_data_layout(
|
||||
"#include <thunks_common.h>\n"
|
||||
"#include <cstdint>\n",
|
||||
"struct A { int64_t a; int32_t b; };\n"
|
||||
"template<> struct fex_gen_type<A> {};\n", guest_abi);
|
||||
|
||||
INFO(FormatDataLayout(action->host_layout));
|
||||
|
||||
REQUIRE(action->guest_layout->contains("A"));
|
||||
CHECK(action->guest_layout->at("A").get_if_struct()->size_bits == (guest_abi == GuestABI::X86_32 ? 96 : 128));
|
||||
|
||||
CHECK(action->GetTypeCompatibility("struct A") == compat_full64_repackable32);
|
||||
}
|
||||
|
||||
SECTION("Different struct definition between guest and host; different member order") {
|
||||
auto action = compute_data_layout(
|
||||
"#include <thunks_common.h>\n"
|
||||
"#include <cstdint>\n",
|
||||
"#ifdef HOST\n"
|
||||
"struct A { int32_t a; int32_t b; };\n"
|
||||
"#else\n"
|
||||
"struct A { int32_t b; int32_t a; };\n"
|
||||
"#endif\n"
|
||||
"template<> struct fex_gen_type<A> {};\n", guest_abi);
|
||||
|
||||
INFO(FormatDataLayout(action->host_layout));
|
||||
|
||||
REQUIRE(action->guest_layout->contains("A"));
|
||||
CHECK(action->guest_layout->at("A").get_if_struct()->members.at(0).member_name == "b");
|
||||
CHECK(action->guest_layout->at("A").get_if_struct()->members.at(1).member_name == "a");
|
||||
|
||||
REQUIRE(!action->host_layout.empty());
|
||||
CHECK(action->host_layout.begin()->second.get_if_struct()->members.at(0).member_name == "a");
|
||||
CHECK(action->host_layout.begin()->second.get_if_struct()->members.at(1).member_name == "b");
|
||||
|
||||
CHECK(action->GetTypeCompatibility("struct A") == TypeCompatibility::Repackable);
|
||||
}
|
||||
|
||||
SECTION("Different struct definition between guest and host; different member size") {
|
||||
auto action = compute_data_layout(
|
||||
"#include <thunks_common.h>\n"
|
||||
"#include <cstdint>\n",
|
||||
"#ifdef HOST\n"
|
||||
"struct A { int32_t a; int32_t b; };\n"
|
||||
"#else\n"
|
||||
"struct A { int32_t a; int64_t b; };\n"
|
||||
"#endif\n"
|
||||
"template<> struct fex_gen_type<A> {};\n", guest_abi);
|
||||
|
||||
INFO(FormatDataLayout(action->host_layout));
|
||||
|
||||
REQUIRE(action->guest_layout->contains("A"));
|
||||
CHECK(action->guest_layout->at("A").get_if_struct()->members.at(0).size_bits == 32);
|
||||
CHECK(action->guest_layout->at("A").get_if_struct()->members.at(1).size_bits == 64);
|
||||
|
||||
REQUIRE(!action->host_layout.empty());
|
||||
CHECK(action->host_layout.begin()->second.get_if_struct()->members.at(0).size_bits == 32);
|
||||
CHECK(action->host_layout.begin()->second.get_if_struct()->members.at(1).size_bits == 32);
|
||||
|
||||
CHECK(action->GetTypeCompatibility("struct A") == TypeCompatibility::Repackable);
|
||||
}
|
||||
|
||||
SECTION("Different struct definition between guest and host; completely different members") {
|
||||
auto action = compute_data_layout(
|
||||
"#include <thunks_common.h>\n"
|
||||
"#include <cstdint>\n",
|
||||
"#ifdef HOST\n"
|
||||
"struct A { int32_t a; int32_t b; };\n"
|
||||
"#else\n"
|
||||
"struct A { int32_t c; int32_t d; };\n"
|
||||
"#endif\n"
|
||||
"template<> struct fex_gen_type<A> {};\n", guest_abi);
|
||||
|
||||
INFO(FormatDataLayout(action->host_layout));
|
||||
|
||||
REQUIRE(action->guest_layout->contains("A"));
|
||||
|
||||
CHECK(action->GetTypeCompatibility("struct A") == TypeCompatibility::None);
|
||||
}
|
||||
|
||||
SECTION("Different struct definition between guest and host; member missing from guest") {
|
||||
auto action = compute_data_layout(
|
||||
"#include <thunks_common.h>\n"
|
||||
"#include <cstdint>\n",
|
||||
"#ifdef HOST\n"
|
||||
"struct A { int32_t a; int32_t b; };\n"
|
||||
"#else\n"
|
||||
"struct A { int32_t a; };\n"
|
||||
"#endif\n"
|
||||
"template<> struct fex_gen_type<A> {};\n", guest_abi);
|
||||
|
||||
INFO(FormatDataLayout(action->host_layout));
|
||||
|
||||
REQUIRE(action->guest_layout->contains("A"));
|
||||
|
||||
CHECK(action->GetTypeCompatibility("struct A") == TypeCompatibility::None);
|
||||
}
|
||||
|
||||
SECTION("Different struct definition between guest and host; member missing from host") {
|
||||
auto action = compute_data_layout(
|
||||
"#include <thunks_common.h>\n"
|
||||
"#include <cstdint>\n",
|
||||
"#ifdef HOST\n"
|
||||
"struct A { int32_t a; };\n"
|
||||
"#else\n"
|
||||
"struct A { int32_t a; int32_t b; };\n"
|
||||
"#endif\n"
|
||||
"template<> struct fex_gen_type<A> {};\n", guest_abi);
|
||||
|
||||
INFO(FormatDataLayout(action->host_layout));
|
||||
|
||||
REQUIRE(action->guest_layout->contains("A"));
|
||||
|
||||
CHECK(action->GetTypeCompatibility("struct A") == TypeCompatibility::None);
|
||||
}
|
||||
|
||||
SECTION("Nesting structs of consistent data layout") {
|
||||
auto action = compute_data_layout(
|
||||
"#include <thunks_common.h>\n"
|
||||
"#include <cstdint>\n",
|
||||
"struct C { int32_t a; int16_t b; };\n"
|
||||
"struct B { C a; int16_t b; };\n"
|
||||
"struct A { int32_t a; B b; };\n"
|
||||
"template<> struct fex_gen_type<A> {};\n", guest_abi);
|
||||
|
||||
INFO(FormatDataLayout(action->host_layout));
|
||||
|
||||
REQUIRE(action->guest_layout->contains("A"));
|
||||
REQUIRE(action->guest_layout->contains("B"));
|
||||
REQUIRE(action->guest_layout->contains("C"));
|
||||
CHECK(action->guest_layout->at("A").get_if_struct()->members.at(0).size_bits == 32);
|
||||
|
||||
CHECK(action->GetTypeCompatibility("struct C") == TypeCompatibility::Full);
|
||||
CHECK(action->GetTypeCompatibility("struct B") == TypeCompatibility::Full);
|
||||
CHECK(action->GetTypeCompatibility("struct A") == TypeCompatibility::Full);
|
||||
}
|
||||
|
||||
SECTION("Nesting repackable structs by embedding") {
|
||||
auto action = compute_data_layout(
|
||||
"#include <thunks_common.h>\n"
|
||||
"#include <cstdint>\n",
|
||||
"#ifdef HOST\n"
|
||||
"struct C { int32_t a; int32_t b; };\n"
|
||||
"#else\n"
|
||||
"struct C { int32_t b; int32_t a; };\n"
|
||||
"#endif\n"
|
||||
"struct B { C a; int16_t b; };\n"
|
||||
"struct A { int32_t a; B b; };\n"
|
||||
"template<> struct fex_gen_type<A> {};\n", guest_abi);
|
||||
|
||||
INFO(FormatDataLayout(action->host_layout));
|
||||
|
||||
REQUIRE(action->guest_layout->contains("A"));
|
||||
REQUIRE(action->guest_layout->contains("B"));
|
||||
REQUIRE(action->guest_layout->contains("C"));
|
||||
CHECK(action->guest_layout->at("A").get_if_struct()->size_bits == 128);
|
||||
CHECK(action->guest_layout->at("A").get_if_struct()->alignment_bits == 32);
|
||||
|
||||
CHECK(action->GetTypeCompatibility("struct C") == TypeCompatibility::Repackable);
|
||||
CHECK(action->GetTypeCompatibility("struct B") == TypeCompatibility::Repackable);
|
||||
CHECK(action->GetTypeCompatibility("struct A") == TypeCompatibility::Repackable);
|
||||
}
|
||||
|
||||
SECTION("Embedded union type (like VkRenderingAttachmentInfo)") {
|
||||
SECTION("without annotation") {
|
||||
CHECK_THROWS_WITH(compute_data_layout(
|
||||
"#include <thunks_common.h>\n"
|
||||
"#include <cstdint>\n",
|
||||
"union B { int32_t a; uint32_t b; };\n"
|
||||
"struct A { B a; };\n"
|
||||
"template<> struct fex_gen_type<A> {};\n", guest_abi),
|
||||
Catch::Contains("unannotated member") && Catch::Contains("union type"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(Fixture, "DataLayoutPointers") {
|
||||
auto guest_abi = GENERATE(GuestABI::X86_32, GuestABI::X86_64);
|
||||
INFO(guest_abi);
|
||||
|
||||
const auto compat_full64_repackable32 = (guest_abi == GuestABI::X86_32 ? TypeCompatibility::Repackable : TypeCompatibility::Full);
|
||||
|
||||
SECTION("Pointer to data with consistent layout") {
|
||||
std::string type = GENERATE("char", "short", "int", "float", "struct B { int a; }");
|
||||
INFO(type);
|
||||
auto action = compute_data_layout(
|
||||
"#include <thunks_common.h>\n"
|
||||
"#include <cstdint>\n",
|
||||
"struct A { " + type + "* a; };\n"
|
||||
"template<> struct fex_gen_type<A> {};\n", guest_abi);
|
||||
|
||||
INFO(FormatDataLayout(action->host_layout));
|
||||
|
||||
REQUIRE(action->guest_layout->contains("A"));
|
||||
// The pointer itself needs repacking on 32-bit. On 64-bit, no repacking is needed at all.
|
||||
CHECK(action->GetTypeCompatibility("struct A") == compat_full64_repackable32);
|
||||
if (!type.starts_with("struct B")) {
|
||||
CHECK(action->GetTypeCompatibility(type) == TypeCompatibility::Full);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("Pointer to struct with consistent layout") {
|
||||
auto action = compute_data_layout(
|
||||
"#include <thunks_common.h>\n"
|
||||
"#include <cstdint>\n",
|
||||
"struct B { int32_t a; };\n"
|
||||
"struct A { B* a; };\n"
|
||||
"template<> struct fex_gen_type<A> {};\n", guest_abi);
|
||||
|
||||
INFO(FormatDataLayout(action->host_layout));
|
||||
|
||||
REQUIRE(action->guest_layout->contains("B"));
|
||||
CHECK(action->GetTypeCompatibility("struct B") == TypeCompatibility::Full);
|
||||
REQUIRE(action->guest_layout->contains("A"));
|
||||
CHECK(action->GetTypeCompatibility("struct A") == compat_full64_repackable32);
|
||||
}
|
||||
|
||||
SECTION("Unannotated pointer to incomplete type") {
|
||||
CHECK_THROWS_WITH(compute_data_layout(
|
||||
"#include <thunks_common.h>\n"
|
||||
"#include <cstdint>\n",
|
||||
"struct B;\n"
|
||||
"struct A { B* a; };\n"
|
||||
"template<> struct fex_gen_type<A> {};\n", guest_abi),
|
||||
Catch::Contains("incomplete type"));
|
||||
}
|
||||
|
||||
SECTION("Unannotated pointer to repackable type") {
|
||||
auto action = compute_data_layout(
|
||||
"#include <thunks_common.h>\n"
|
||||
"#include <cstdint>\n",
|
||||
"#ifdef HOST\n"
|
||||
"struct B { int32_t a; int32_t b; };\n"
|
||||
"#else\n"
|
||||
"struct B { int32_t a; int64_t b; };\n"
|
||||
"#endif\n"
|
||||
"struct A { B* a; };\n"
|
||||
"template<> struct fex_gen_type<A> {};\n", guest_abi);
|
||||
|
||||
INFO(FormatDataLayout(action->host_layout));
|
||||
|
||||
REQUIRE(action->guest_layout->contains("A"));
|
||||
CHECK(action->GetTypeCompatibility("struct A") == TypeCompatibility::None);
|
||||
}
|
||||
|
||||
SECTION("Nesting repackable structs by pointers") {
|
||||
SECTION("Innermost struct is compatible") {
|
||||
auto action = compute_data_layout(
|
||||
"#include <thunks_common.h>\n"
|
||||
"#include <cstdint>\n",
|
||||
"struct C { int32_t a; int32_t b; };\n"
|
||||
"struct B { C* a; int16_t b; };\n"
|
||||
"struct A { int32_t a; B b; };\n"
|
||||
"template<> struct fex_gen_type<A> {};\n", guest_abi);
|
||||
|
||||
INFO(FormatDataLayout(action->host_layout));
|
||||
|
||||
REQUIRE(action->guest_layout->contains("A"));
|
||||
REQUIRE(action->guest_layout->contains("B"));
|
||||
REQUIRE(action->guest_layout->contains("C"));
|
||||
|
||||
// 64-bit is fully compatible, but 32-bit needs to zero-extend the pointer itself
|
||||
CHECK(action->GetTypeCompatibility("struct C") == TypeCompatibility::Full);
|
||||
CHECK(action->GetTypeCompatibility("struct B") == compat_full64_repackable32);
|
||||
CHECK(action->GetTypeCompatibility("struct A") == compat_full64_repackable32);
|
||||
}
|
||||
|
||||
SECTION("Innermost struct is incompatible") {
|
||||
auto action = compute_data_layout(
|
||||
"#include <thunks_common.h>\n"
|
||||
"#include <cstdint>\n",
|
||||
"#ifdef HOST\n"
|
||||
"struct C { int32_t a; int32_t b; };\n"
|
||||
"#else\n"
|
||||
"struct C { int32_t b; int32_t a; };\n"
|
||||
"#endif\n"
|
||||
"struct B { C* a; int16_t b; };\n"
|
||||
"struct A { int32_t a; B b; };\n"
|
||||
"template<> struct fex_gen_type<A> {};\n", guest_abi);
|
||||
|
||||
INFO(FormatDataLayout(action->host_layout));
|
||||
|
||||
REQUIRE(action->guest_layout->contains("A"));
|
||||
REQUIRE(action->guest_layout->contains("B"));
|
||||
REQUIRE(action->guest_layout->contains("C"));
|
||||
|
||||
CHECK(action->GetTypeCompatibility("struct C") == TypeCompatibility::Repackable);
|
||||
CHECK(action->GetTypeCompatibility("struct B") == TypeCompatibility::None);
|
||||
CHECK(action->GetTypeCompatibility("struct A") == TypeCompatibility::None);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("Unannotated pointer to union type") {
|
||||
CHECK_THROWS_WITH(compute_data_layout(
|
||||
"#include <thunks_common.h>\n"
|
||||
"#include <cstdint>\n",
|
||||
"union B { int32_t a; uint32_t b; };\n"
|
||||
"struct A { B* a; };\n"
|
||||
"template<> struct fex_gen_type<A> {};\n", guest_abi),
|
||||
Catch::Contains("unannotated member") && Catch::Contains("union type"));
|
||||
}
|
||||
|
||||
SECTION("Self-referencing struct (like VkBaseOutStructure)") {
|
||||
// Without annotation
|
||||
auto action = compute_data_layout(
|
||||
"#include <thunks_common.h>\n"
|
||||
"#include <cstdint>\n",
|
||||
"struct A { A* a; };\n"
|
||||
"template<> struct fex_gen_type<A> {};\n", guest_abi);
|
||||
|
||||
INFO(FormatDataLayout(action->host_layout));
|
||||
|
||||
REQUIRE(action->guest_layout->contains("A"));
|
||||
CHECK_THROWS_WITH(action->GetTypeCompatibility("struct A"), Catch::Contains("recursive reference"));
|
||||
}
|
||||
|
||||
SECTION("Circularly referencing structs") {
|
||||
// Without annotation
|
||||
auto action = compute_data_layout(
|
||||
"#include <thunks_common.h>\n"
|
||||
"#include <cstdint>\n",
|
||||
"struct B;\n"
|
||||
"struct A { B* a; };\n"
|
||||
"struct B { A* a; };\n"
|
||||
"template<> struct fex_gen_type<A> {};\n", guest_abi);
|
||||
|
||||
INFO(FormatDataLayout(action->host_layout));
|
||||
|
||||
REQUIRE(action->guest_layout->contains("A"));
|
||||
REQUIRE(action->guest_layout->contains("B"));
|
||||
CHECK_THROWS_WITH(action->GetTypeCompatibility("struct A"), Catch::Contains("recursive reference"));
|
||||
CHECK_THROWS_WITH(action->GetTypeCompatibility("struct B"), Catch::Contains("recursive reference"));
|
||||
}
|
||||
|
||||
SECTION("Pointers to void") {
|
||||
// Without annotation
|
||||
auto action = compute_data_layout(
|
||||
"#include <thunks_common.h>\n"
|
||||
"#include <cstdint>\n",
|
||||
"struct A { void* a; };\n"
|
||||
"template<> struct fex_gen_type<A> {};\n", guest_abi);
|
||||
|
||||
INFO(FormatDataLayout(action->host_layout));
|
||||
|
||||
REQUIRE(action->guest_layout->contains("A"));
|
||||
CHECK(action->GetTypeCompatibility("struct A") == (guest_abi == GuestABI::X86_32 ? TypeCompatibility::None : TypeCompatibility::Full));
|
||||
}
|
||||
|
||||
// TODO: Double pointers to compatible data: struct B { int a ; }; struct A { B** b; };
|
||||
}
|
||||
@@ -42,15 +42,42 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
enum class GuestABI {
|
||||
X86_32,
|
||||
X86_64,
|
||||
};
|
||||
|
||||
inline std::ostream& operator<<(std::ostream& os, GuestABI abi) {
|
||||
if (abi == GuestABI::X86_32) {
|
||||
os << "X86_32";
|
||||
} else if (abi == GuestABI::X86_64) {
|
||||
os << "X86_64";
|
||||
}
|
||||
return os;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the given ToolAction on the input code.
|
||||
*
|
||||
* The "silent" parameter is used to suppress non-fatal diagnostics in tests that expect failure
|
||||
*/
|
||||
inline void run_tool(clang::tooling::ToolAction& action, std::string_view code, bool silent = false) {
|
||||
inline void run_tool(clang::tooling::ToolAction& action, std::string_view code, bool silent = false, std::optional<GuestABI> guest_abi = std::nullopt) {
|
||||
const char* memory_filename = "gen_input.cpp";
|
||||
auto adjuster = clang::tooling::getClangStripDependencyFileAdjuster();
|
||||
std::vector<std::string> args = { "clang-tool", "-fsyntax-only", "-std=c++17", "-Werror", "-I.", memory_filename };
|
||||
if (guest_abi == GuestABI::X86_64) {
|
||||
args.push_back("-target");
|
||||
args.push_back("x86_64-linux-gnu");
|
||||
args.push_back("-isystem");
|
||||
args.push_back("/usr/x86_64-linux-gnu/include/");
|
||||
} else if (guest_abi == GuestABI::X86_32) {
|
||||
args.push_back("-target");
|
||||
args.push_back("i686-linux-gnu");
|
||||
args.push_back("-isystem");
|
||||
args.push_back("/usr/i686-linux-gnu/include/");
|
||||
} else {
|
||||
args.push_back("-DHOST");
|
||||
}
|
||||
|
||||
// Corresponds to the content of GeneratorInterface.h
|
||||
const char* common_header_code = R"(namespace fexgen {
|
||||
@@ -60,6 +87,12 @@ struct callback_annotation_base { bool prevent_multiple; };
|
||||
struct callback_stub : callback_annotation_base {};
|
||||
struct callback_guest : callback_annotation_base {};
|
||||
} // namespace fexgen
|
||||
|
||||
template<typename>
|
||||
struct fex_gen_type;
|
||||
template<auto>
|
||||
struct fex_gen_config;
|
||||
|
||||
)";
|
||||
|
||||
llvm::IntrusiveRefCntPtr<llvm::vfs::OverlayFileSystem> overlay_fs(new llvm::vfs::OverlayFileSystem(llvm::vfs::getRealFileSystem()));
|
||||
@@ -73,6 +106,9 @@ struct callback_guest : callback_annotation_base {};
|
||||
|
||||
TestDiagnosticConsumer consumer(silent);
|
||||
invocation.setDiagnosticConsumer(&consumer);
|
||||
|
||||
// Process the actual ToolAction.
|
||||
// NOTE: If the ToolAction throws an exception, clang will leak memory here.
|
||||
invocation.run();
|
||||
|
||||
if (auto error = consumer.GetFirstError()) {
|
||||
@@ -80,6 +116,6 @@ struct callback_guest : callback_annotation_base {};
|
||||
}
|
||||
}
|
||||
|
||||
inline void run_tool(std::unique_ptr<clang::tooling::ToolAction> action, std::string_view code, bool silent = false) {
|
||||
return run_tool(*action, code, silent);
|
||||
inline void run_tool(std::unique_ptr<clang::tooling::ToolAction> action, std::string_view code, bool silent = false, std::optional<GuestABI> guest_abi = std::nullopt) {
|
||||
return run_tool(*action, code, silent, guest_abi);
|
||||
}
|
||||
Reference in new issue
Block a user