From b04b0549a9969539a4307eed9eafcbf56148f9cf Mon Sep 17 00:00:00 2001 From: Tony Wasserka Date: Mon, 25 Sep 2023 23:10:03 +0200 Subject: [PATCH] unittests/ThunkLibs: Add data layout tests --- unittests/ThunkLibs/CMakeLists.txt | 3 +- unittests/ThunkLibs/abi.cpp | 643 +++++++++++++++++++++++++++++ unittests/ThunkLibs/common.h | 42 +- 3 files changed, 684 insertions(+), 4 deletions(-) create mode 100644 unittests/ThunkLibs/abi.cpp diff --git a/unittests/ThunkLibs/CMakeLists.txt b/unittests/ThunkLibs/CMakeLists.txt index ccde2918f..8618eaa66 100644 --- a/unittests/ThunkLibs/CMakeLists.txt +++ b/unittests/ThunkLibs/CMakeLists.txt @@ -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") diff --git a/unittests/ThunkLibs/abi.cpp b/unittests/ThunkLibs/abi.cpp new file mode 100644 index 000000000..ca9b193c5 --- /dev/null +++ b/unittests/ThunkLibs/abi.cpp @@ -0,0 +1,643 @@ +#include +#include + +#include +#include +#include "common.h" + +#include + +#include + +// 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 compute_data_layout(std::string_view prelude, std::string_view code, GuestABI); +}; + +} + +class DataLayoutCompareActionForTest : public DataLayoutCompareAction { + std::unordered_map type_compat_cache; + + // Persistent reference taken to enable accessing the ASTContext after CompilerInstance::ExecuteAction returns + llvm::IntrusiveRefCntPtr ast_context; + std::shared_ptr preprocessor; + +public: + DataLayoutCompareActionForTest(std::unique_ptr 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 guest_layout; + std::unordered_map 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 invocation, clang::FileManager *files, + std::shared_ptr 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 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(); + run_tool(*data_layout_analysis_factory, full_code, false, guest_abi); + + // Compute host data layout + auto ScopedToolAction = std::make_unique(data_layout_analysis_factory->TakeDataLayout()); + run_tool(std::make_unique(*ScopedToolAction), full_code, false, std::nullopt); + + return ScopedToolAction; +} + +static std::string FormatDataLayout(const std::unordered_map& 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 \n", + "struct A { int a; };\n" + "template<> struct fex_gen_type {};\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 \n", + "struct A { char a; short b; int c; float d; };\n" + "template<> struct fex_gen_type {};\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 \n" + "#include \n", + "struct A { int16_t a; int32_t b; };\n" + "template<> struct fex_gen_type {};\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 \n" + "#include \n", + "struct A { int16_t a[64]; };\n" + "template<> struct fex_gen_type {};\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 \n" + "#include \n", + "struct A { size_t a; };\n" + "template<> struct fex_gen_type {};\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 \n" + "#include \n", + "struct A { int64_t a; };\n" + "template<> struct fex_gen_type {};\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 \n" + "#include \n", + "struct A { int64_t a[64]; };\n" + "template<> struct fex_gen_type {};\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 \n" + "#include \n", + "struct alignas(8) A { int64_t a; };\n" + "template<> struct fex_gen_type {};\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 \n" + "#include \n", + "struct A { int32_t a; int32_t b; int64_t c; };\n" + "template<> struct fex_gen_type {};\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 \n" + "#include \n", + "struct A { int32_t a; int64_t b; };\n" + "template<> struct fex_gen_type {};\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 \n" + "#include \n", + "struct A { int64_t a; int32_t b; };\n" + "template<> struct fex_gen_type {};\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 \n" + "#include \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 {};\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 \n" + "#include \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 {};\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 \n" + "#include \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 {};\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 \n" + "#include \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 {};\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 \n" + "#include \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 {};\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 \n" + "#include \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 {};\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 \n" + "#include \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 {};\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 \n" + "#include \n", + "union B { int32_t a; uint32_t b; };\n" + "struct A { B a; };\n" + "template<> struct fex_gen_type {};\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 \n" + "#include \n", + "struct A { " + type + "* a; };\n" + "template<> struct fex_gen_type {};\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 \n" + "#include \n", + "struct B { int32_t a; };\n" + "struct A { B* a; };\n" + "template<> struct fex_gen_type {};\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 \n" + "#include \n", + "struct B;\n" + "struct A { B* a; };\n" + "template<> struct fex_gen_type {};\n", guest_abi), + Catch::Contains("incomplete type")); + } + + SECTION("Unannotated pointer to repackable type") { + auto action = compute_data_layout( + "#include \n" + "#include \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 {};\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 \n" + "#include \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 {};\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 \n" + "#include \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 {};\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 \n" + "#include \n", + "union B { int32_t a; uint32_t b; };\n" + "struct A { B* a; };\n" + "template<> struct fex_gen_type {};\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 \n" + "#include \n", + "struct A { A* a; };\n" + "template<> struct fex_gen_type {};\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 \n" + "#include \n", + "struct B;\n" + "struct A { B* a; };\n" + "struct B { A* a; };\n" + "template<> struct fex_gen_type {};\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 \n" + "#include \n", + "struct A { void* a; };\n" + "template<> struct fex_gen_type {};\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; }; +} diff --git a/unittests/ThunkLibs/common.h b/unittests/ThunkLibs/common.h index 47c83bd9b..d31b10410 100644 --- a/unittests/ThunkLibs/common.h +++ b/unittests/ThunkLibs/common.h @@ -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 guest_abi = std::nullopt) { const char* memory_filename = "gen_input.cpp"; auto adjuster = clang::tooling::getClangStripDependencyFileAdjuster(); std::vector 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 +struct fex_gen_type; +template +struct fex_gen_config; + )"; llvm::IntrusiveRefCntPtr 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 action, std::string_view code, bool silent = false) { - return run_tool(*action, code, silent); +inline void run_tool(std::unique_ptr action, std::string_view code, bool silent = false, std::optional guest_abi = std::nullopt) { + return run_tool(*action, code, silent, guest_abi); }