diff --git a/core/libs/prx/libSceFreeType/include/GuestList.hpp b/core/libs/prx/libSceFreeType/include/GuestList.hpp index 67a8396b87..1e11a349c1 100644 --- a/core/libs/prx/libSceFreeType/include/GuestList.hpp +++ b/core/libs/prx/libSceFreeType/include/GuestList.hpp @@ -3,16 +3,21 @@ #include "prx/libc/include/general/VabiMacros.hpp" -#include -#include FT_LIST_H - #include #include namespace FreeType { -using GuestListRec = FT_ListRec; -using GuestListNodeRec = FT_ListNodeRec; +struct GuestListNodeRec { + GuestListNodeRec* prev; + GuestListNodeRec* next; + void* data; +}; + +struct GuestListRec { + GuestListNodeRec* head; + GuestListNodeRec* tail; +}; struct GuestMemoryRec { void* user; diff --git a/core/libs/prx/libSceFreeType/src/List.cpp b/core/libs/prx/libSceFreeType/src/List.cpp index 45a1872a31..198f3207b6 100644 --- a/core/libs/prx/libSceFreeType/src/List.cpp +++ b/core/libs/prx/libSceFreeType/src/List.cpp @@ -4,6 +4,8 @@ #include #include +#include +#include FT_LIST_H using namespace FreeType; @@ -26,77 +28,89 @@ private: std::array writes; }; -struct Iteration { - GuestListIterator iterator; - void* user; -}; - -int Iterate(FT_ListNode node, void* user) { - auto& call = *static_cast(user); - return call.iterator(node, call.user); -} - -struct Finalization { - GuestMemoryRec* memory; - GuestListDestructor destroy; - void* user; -}; - -void Destroy(FT_Memory memory, void* data, void*) { - auto& call = *static_cast(memory->user); - call.destroy(call.memory, data, call.user); -} - -void FreeNode(FT_Memory memory, void* block) { - auto& call = *static_cast(memory->user); - call.memory->free(call.memory, block); -} - } extern "C" { GuestListNodeRec* APS5_VABI FT_List_Find_nid_postfix(GuestListRec* list, void* data) { - return FT_List_Find(list, data); + if (!list) return nullptr; + for (auto* node = list->head; node; node = node->next) { + if (node->data == data) return node; + } + return nullptr; } void APS5_VABI FT_List_Add_nid_postfix(GuestListRec* list, GuestListNodeRec* node) { if (!list || !node) return; const ListWrite write(list, {node, list->tail}); - FT_List_Add(list, node); + auto* tail = list->tail; + node->next = nullptr; + node->prev = tail; + if (tail) tail->next = node; + else list->head = node; + list->tail = node; } void APS5_VABI FT_List_Insert_nid_postfix(GuestListRec* list, GuestListNodeRec* node) { if (!list || !node) return; const ListWrite write(list, {node, list->head}); - FT_List_Insert(list, node); + auto* head = list->head; + node->next = head; + node->prev = nullptr; + if (head) head->prev = node; + else list->tail = node; + list->head = node; } void APS5_VABI FT_List_Remove_nid_postfix(GuestListRec* list, GuestListNodeRec* node) { if (!list || !node) return; const ListWrite write(list, {node->prev, node->next}); - FT_List_Remove(list, node); + auto* previous = node->prev; + auto* next = node->next; + if (previous) previous->next = next; + else list->head = next; + if (next) next->prev = previous; + else list->tail = previous; } void APS5_VABI FT_List_Up_nid_postfix(GuestListRec* list, GuestListNodeRec* node) { if (!list || !node || !node->prev) return; const ListWrite write(list, {node, node->prev, node->next, list->head}); - FT_List_Up(list, node); + auto* previous = node->prev; + auto* next = node->next; + previous->next = next; + if (next) next->prev = previous; + else list->tail = previous; + node->prev = nullptr; + node->next = list->head; + list->head->prev = node; + list->head = node; } int APS5_VABI FT_List_Iterate_nid_postfix(GuestListRec* list, GuestListIterator iterator, void* user) { - Iteration call{iterator, user}; - return FT_List_Iterate(list, iterator ? Iterate : nullptr, &call); + if (!list || !iterator) return FT_Err_Invalid_Argument; + auto* node = list->head; + while (node) { + auto* next = node->next; + const int error = iterator(node, user); + if (error) return error; + node = next; + } + return 0; } void APS5_VABI FT_List_Finalize_nid_postfix(GuestListRec* list, GuestListDestructor destroy, GuestMemoryRec* memory, void* user) { if (!list || !memory) return; const ListWrite write(list); - Finalization call{memory, destroy, user}; - FT_MemoryRec_ native{}; - native.user = &call; - native.free = FreeNode; - FT_List_Finalize(list, destroy ? Destroy : nullptr, &native, user); + auto* node = list->head; + while (node) { + auto* next = node->next; + if (destroy) destroy(memory, node->data, user); + memory->free(memory, node); + node = next; + } + list->head = nullptr; + list->tail = nullptr; } } diff --git a/core/libs/tests/GuestFreeTypeList.cpp b/core/libs/tests/GuestFreeTypeList.cpp index 0e93a0d850..f853e00bbb 100644 --- a/core/libs/tests/GuestFreeTypeList.cpp +++ b/core/libs/tests/GuestFreeTypeList.cpp @@ -7,6 +7,9 @@ #include #include #include +#include +#include +#include FT_LIST_H using namespace FreeType; @@ -20,9 +23,11 @@ static void Check(bool value, int line) { #define Require(value) Check((value), __LINE__) constexpr std::size_t Count = 16; -using Nodes = std::array; +template +using Nodes = std::array; -static int Index(FT_ListNode node, const Nodes& nodes) { +template +static int Index(TNode* node, const Nodes& nodes) { if (!node) return -1; for (std::size_t i = 0; i < nodes.size(); ++i) if (node == &nodes[i]) return static_cast(i); Require(false); @@ -30,10 +35,10 @@ static int Index(FT_ListNode node, const Nodes& nodes) { } struct Lists { - FT_ListRec guest{}; + GuestListRec guest{}; FT_ListRec native{}; - Nodes actual{}; - Nodes expected{}; + Nodes actual{}; + Nodes expected{}; std::array values{11, 22, 33, 44}; Lists() { @@ -114,9 +119,10 @@ static void Mutations() { std::printf("list mutations: 2048 states compared with native FreeType\n"); } +template struct Walk { - FT_List list; - Nodes* nodes; + std::conditional_t, GuestListRec, FT_ListRec>* list; + Nodes* nodes; bool guest; bool remove; std::size_t stop; @@ -124,12 +130,13 @@ struct Walk { std::vector visits; }; -static int Visit(FT_ListNode node, void* user, bool guest) { - auto& state = *static_cast(user); +template +static int Visit(TNode* node, void* user, bool guest) { + auto& state = *static_cast*>(user); Require(state.guest == guest); state.visits.push_back(Index(node, *state.nodes)); if (state.remove) { - if (guest) FT_List_Remove_nid_postfix(state.list, node); + if constexpr (std::is_same_v) FT_List_Remove_nid_postfix(state.list, node); else FT_List_Remove(state.list, node); node->prev = nullptr; node->next = nullptr; @@ -172,8 +179,9 @@ struct Event { bool operator==(const Event&) const = default; }; +template struct Disposal { - FT_List list; + TList* list; FT_Memory native; GuestMemoryRec* guest; void* user; @@ -181,34 +189,36 @@ struct Disposal { std::vector events; }; -static void Release(Disposal& state, void* block) { +template +static void Release(Disposal& state, void* block) { const auto found = state.live.find(block); Require(found != state.live.end()); state.events.push_back({2, found->second, state.list->head == nullptr || state.list->tail == nullptr}); state.live.erase(found); - delete static_cast(block); + using Node = std::conditional_t, GuestListNodeRec, FT_ListNodeRec>; + delete static_cast(block); } static void NativeFree(FT_Memory memory, void* block) { - auto& state = *static_cast(memory->user); + auto& state = *static_cast*>(memory->user); Require(state.native == memory); Release(state, block); } static void APS5_VABI GuestFree(GuestMemoryRec* memory, void* block) { - auto& state = *static_cast(memory->user); + auto& state = *static_cast*>(memory->user); Require(state.guest == memory); Release(state, block); } static void NativeDestroy(FT_Memory memory, void* data, void* user) { - auto& state = *static_cast(memory->user); + auto& state = *static_cast*>(memory->user); Require(state.native == memory && state.user == user); state.events.push_back({1, data ? *static_cast(data) : -1, state.list->head == nullptr || state.list->tail == nullptr}); } static void APS5_VABI GuestDestroy(GuestMemoryRec* memory, void* data, void* user) { - auto& state = *static_cast(memory->user); + auto& state = *static_cast*>(memory->user); Require(state.guest == memory && state.user == user); state.events.push_back({1, data ? *static_cast(data) : -1, state.list->head == nullptr || state.list->tail == nullptr}); } @@ -216,7 +226,8 @@ static void APS5_VABI GuestDestroy(GuestMemoryRec* memory, void* data, void* use static void FinalizationCases() { for (const bool destroy : {false, true}) { for (const unsigned count : {0u, 1u, 8u}) { - FT_ListRec guestList{}, nativeList{}; + GuestListRec guestList{}; + FT_ListRec nativeList{}; std::array ids{0, 1, 2, 3, 4, 5, 6, 7}; int token = 9; Disposal guest{&guestList, nullptr, nullptr, &token, {}, {}}; @@ -226,7 +237,7 @@ static void FinalizationCases() { guest.guest = &guestMemory; native.native = &nativeMemory; for (unsigned i = 0; i < count; ++i) { - auto* a = new FT_ListNodeRec{}; + auto* a = new GuestListNodeRec{}; auto* b = new FT_ListNodeRec{}; a->data = b->data = i == 0 ? nullptr : &ids[i]; guest.live.emplace(a, i); diff --git a/core/libs/tests/test_public_headers.py b/core/libs/tests/test_public_headers.py index 8aa05f692b..2511659765 100644 --- a/core/libs/tests/test_public_headers.py +++ b/core/libs/tests/test_public_headers.py @@ -5,6 +5,7 @@ import json import re import subprocess import sys +import tempfile from functools import cache from pathlib import Path @@ -171,12 +172,30 @@ def compile_headers(compiler, compiler_args, groups, workers=4): def compile_one(group): includes = "".join(f'#include "{header.relative_to(LIBS).as_posix()}"\n' for header in group) - result = subprocess.run([compiler, *compiler_args, "-std=c++20", *warnings, "-fsyntax-only", "-x", "c++", f"-I{LIBS}", f"-I{ROOT / '3rdparty/freetype/include'}", "-"], input=includes * 2, text=True, capture_output=True) - if result.returncode: - raise RuntimeError(f"Header compilation failed: {group}\n{result.stdout}{result.stderr}") + with tempfile.TemporaryDirectory() as directory: + dependencies = Path(directory) / "headers.d" + result = subprocess.run([compiler, *compiler_args, "-std=c++20", *warnings, "-fsyntax-only", "-MD", "-MF", str(dependencies), "-MT", "public_headers", "-x", "c++", f"-I{LIBS}", f"-I{ROOT / '3rdparty/freetype/include'}", "-"], input=includes * 2, text=True, capture_output=True) + if result.returncode: + raise RuntimeError(f"Header compilation failed: {group}\n{result.stdout}{result.stderr}") + text = dependencies.read_text(encoding="utf-8").replace("\\\n", "") + if not text.startswith("public_headers:"): + raise RuntimeError(f"Invalid compiler dependency output: {dependencies}") + names = re.findall(r"(?:\\[ \t]|[^\s])+", text.removeprefix("public_headers:")) + paths = [Path(re.sub(r"\\([ \t#\\])", r"\1", name).replace("$$", "$")).resolve() for name in names] + forbidden = [path for path in paths if path.is_relative_to((ROOT / "3rdparty").resolve())] + if forbidden: + raise RuntimeError(f"Public headers depend on 3rdparty: {group}\n" + "\n".join(map(str, forbidden))) + errors = [] with concurrent.futures.ThreadPoolExecutor(max_workers=workers) as pool: - list(pool.map(compile_one, groups)) + futures = [(group, pool.submit(compile_one, group)) for group in groups] + for group, future in futures: + try: + future.result() + except Exception as error: + errors.append(f"{group}: {error}") + if errors: + raise RuntimeError(f"{len(errors)} header compilation units failed:\n\n" + "\n\n".join(errors)) def main():