diff --git a/core/libs/CMakeLists.txt b/core/libs/CMakeLists.txt index ee9c993f5..b86ddf5c5 100644 --- a/core/libs/CMakeLists.txt +++ b/core/libs/CMakeLists.txt @@ -363,6 +363,14 @@ if(BUILD_TESTING) set_tests_properties(guest_json PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_executable(fiber_tests tests/Fiber.cpp) + target_include_directories(fiber_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(fiber_tests PRIVATE libSceFiber) + add_test(NAME fiber COMMAND fiber_tests) + if(WIN32) + set_tests_properties(fiber PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_executable(mspace_tests tests/Mspace.cpp) target_include_directories(mspace_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) target_link_libraries(mspace_tests PRIVATE libc) diff --git a/core/libs/prx/libSceFiber/Export.cpp b/core/libs/prx/libSceFiber/Export.cpp index b24a8ea92..adbe258a5 100644 --- a/core/libs/prx/libSceFiber/Export.cpp +++ b/core/libs/prx/libSceFiber/Export.cpp @@ -27,8 +27,6 @@ static constexpr std::size_t FIBER_OBJECT_SIZE = 0x100; static constexpr std::size_t FIBER_OPT_PARAM_SIZE = 0x80; static constexpr std::size_t FIBER_MIN_CONTEXT_SIZE = 512; -#ifdef _WIN32 - using GuestFiberEntry = void (APS5_VABI*)(std::uint64_t argOnInitialize, std::uint64_t argOnRun); // A fiber that is switching away stays Suspending until its context is saved; whoever runs next @@ -92,6 +90,8 @@ static void CompletePendingSuspend() { } } +#ifdef _WIN32 + extern "C" void Aps5FiberSwitchStack(void** save, void* load); extern "C" void Aps5FiberTrampoline(); @@ -180,6 +180,68 @@ static void SetBounds(const StackBounds& bounds) { *reinterpret_cast(teb + 0x1478) = bounds.deallocation; } +#else + +extern "C" __attribute__((visibility("hidden"))) void Aps5FiberSwitchStack(void** save, void* load); +extern "C" __attribute__((visibility("hidden"))) void Aps5FiberTrampoline(); + +asm(R"( + .text + .globl Aps5FiberSwitchStack + .hidden Aps5FiberSwitchStack + .type Aps5FiberSwitchStack, @function +Aps5FiberSwitchStack: + push %rbp + push %rbx + push %r12 + push %r13 + push %r14 + push %r15 + sub $8, %rsp + stmxcsr 0(%rsp) + fnstcw 4(%rsp) + mov %rsp, (%rdi) + mov %rsi, %rsp + ldmxcsr 0(%rsp) + fldcw 4(%rsp) + add $8, %rsp + pop %r15 + pop %r14 + pop %r13 + pop %r12 + pop %rbx + pop %rbp + ret + .size Aps5FiberSwitchStack, .-Aps5FiberSwitchStack + + .globl Aps5FiberTrampoline + .hidden Aps5FiberTrampoline + .type Aps5FiberTrampoline, @function +Aps5FiberTrampoline: + mov %r12, %rdi + and $-16, %rsp + call Aps5FiberMain + ud2 + .size Aps5FiberTrampoline, .-Aps5FiberTrampoline +)"); + +struct InitialFrame { + std::uint32_t mxcsr; + std::uint16_t fpuControl; + std::uint16_t padding; + std::uint64_t r15, r14, r13, r12, rbx, rbp; + std::uint64_t returnAddress; +}; +static_assert(sizeof(InitialFrame) == 8 + 6 * 8 + 8, "initial fiber frame must match Aps5FiberSwitchStack"); + +static StackBounds CurrentBounds() { + return {}; +} + +static void SetBounds(const StackBounds&) {} + +#endif + static StackBounds FiberBounds(const Fiber* fiber) { return {fiber->context + fiber->contextSize, fiber->context, fiber->context}; } @@ -189,7 +251,7 @@ static Fiber* AsFiber(FiberObject* object) { return fiber && fiber->magic == FIBER_MAGIC ? fiber : nullptr; } -extern "C" [[noreturn]] void Aps5FiberMain(Fiber* fiber) { +extern "C" [[noreturn]] __attribute__((visibility("hidden"))) void Aps5FiberMain(Fiber* fiber) { CompletePendingSuspend(); fiber->entry(fiber->argOnInitialize, ThreadState().transfer); throw std::runtime_error(std::string("sceFiber: entry function of fiber '") + fiber->name + "' returned"); @@ -377,23 +439,3 @@ int32_t APS5_VABI sceFiberStopContextSizeCheck(void) { } -#else - -extern "C" { - -int32_t APS5_VABI _sceFiberInitializeImpl_nid_postfix() { NotImplemented_nid_no_patch(__func__); return 0; } -int32_t APS5_VABI sceFiberFinalize(FiberObject*) { NotImplemented_nid_no_patch(__func__); return 0; } -int32_t APS5_VABI sceFiberRun_nid_postfix(FiberObject*, uint64_t, uint64_t*) { NotImplemented_nid_no_patch(__func__); return 0; } -int32_t APS5_VABI sceFiberSwitch(FiberObject*, uint64_t, uint64_t*) { NotImplemented_nid_no_patch(__func__); return 0; } -int32_t APS5_VABI sceFiberReturnToThread(uint64_t, uint64_t*) { NotImplemented_nid_no_patch(__func__); return 0; } -int32_t APS5_VABI sceFiberGetSelf(FiberObject**) { NotImplemented_nid_no_patch(__func__); return 0; } -int32_t APS5_VABI sceFiberGetInfo(FiberObject*, FiberInfo*) { NotImplemented_nid_no_patch(__func__); return 0; } -int32_t APS5_VABI sceFiberRename(FiberObject*, const char*) { NotImplemented_nid_no_patch(__func__); return 0; } -int32_t APS5_VABI sceFiberOptParamInitialize(FiberOptParam*) { NotImplemented_nid_no_patch(__func__); return 0; } -int32_t APS5_VABI sceFiberGetThreadFramePointerAddress(uint64_t*) { NotImplemented_nid_no_patch(__func__); return 0; } -int32_t APS5_VABI sceFiberStartContextSizeCheck(uint32_t) { NotImplemented_nid_no_patch(__func__); return 0; } -int32_t APS5_VABI sceFiberStopContextSizeCheck(void) { NotImplemented_nid_no_patch(__func__); return 0; } - -} - -#endif diff --git a/core/libs/tests/Fiber.cpp b/core/libs/tests/Fiber.cpp new file mode 100644 index 000000000..2f0109084 --- /dev/null +++ b/core/libs/tests/Fiber.cpp @@ -0,0 +1,81 @@ +#include "SceTypes.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include + +using Entry = void (APS5_VABI*)(std::uint64_t, std::uint64_t); + +extern "C" { +std::int32_t APS5_VABI _sceFiberInitializeImpl_nid_postfix(FiberObject*, const char*, Entry, std::uint64_t, void*, std::uint64_t, const void*, std::uint32_t); +std::int32_t APS5_VABI sceFiberFinalize(FiberObject*); +std::int32_t APS5_VABI sceFiberRun_nid_postfix(FiberObject*, std::uint64_t, std::uint64_t*); +std::int32_t APS5_VABI sceFiberSwitch(FiberObject*, std::uint64_t, std::uint64_t*); +std::int32_t APS5_VABI sceFiberReturnToThread(std::uint64_t, std::uint64_t*); +std::int32_t APS5_VABI sceFiberGetSelf(FiberObject**); +} + +static void Require(bool value, const char* what) { + if (!value) { std::fprintf(stderr, "Fiber check failed: %s\n", what); std::abort(); } +} + +namespace { + +constexpr std::int32_t FiberErrorPermission = static_cast(0x80590005); +constexpr std::int32_t FiberErrorState = static_cast(0x80590006); + +alignas(16) std::array g_firstContext; +alignas(16) std::array g_secondContext; +struct alignas(8) FiberStorage { + unsigned char bytes[0x100]; +}; + +FiberStorage g_firstStorage; +FiberStorage g_secondStorage; +auto* const g_first = reinterpret_cast(&g_firstStorage); +auto* const g_second = reinterpret_cast(&g_secondStorage); + +void APS5_VABI FirstEntry(std::uint64_t argOnInitialize, std::uint64_t argOnRun) { + Require(argOnInitialize == 11 && argOnRun == 100, "first fiber arguments"); + FiberObject* self = nullptr; + Require(sceFiberGetSelf(&self) == 0 && self == g_first, "first fiber self"); + volatile double carried = 1.5; + std::uint64_t received = 0; + Require(sceFiberSwitch(g_second, 200, &received) == 0, "switch to second fiber"); + Require(received == 300 && carried == 1.5, "switch back to first fiber"); + for (;;) { + Require(sceFiberReturnToThread(received + 1, &received) == 0, "first fiber return"); + } +} + +void APS5_VABI SecondEntry(std::uint64_t argOnInitialize, std::uint64_t argOnRun) { + Require(argOnInitialize == 22 && argOnRun == 200, "second fiber arguments"); + Require(sceFiberSwitch(g_second, 0, nullptr) == FiberErrorState, "switch to self"); + _mm_setcsr(_mm_getcsr() | 0x8000u); + std::uint64_t received = 0; + Require(sceFiberReturnToThread(250, &received) == 0 && received == 260, "second fiber resumed on another thread"); + Require((_mm_getcsr() & 0x8000u) != 0, "second fiber keeps its MXCSR"); + Require(sceFiberSwitch(g_first, 300, nullptr) == 0, "switch to first fiber"); + Require(false, "second fiber resumed after its last switch"); +} + +} + +int main() { + FiberObject* self = nullptr; + Require(sceFiberGetSelf(&self) == FiberErrorPermission, "no fiber on the thread"); + Require(_sceFiberInitializeImpl_nid_postfix(g_first, "first", FirstEntry, 11, g_firstContext.data(), g_firstContext.size(), nullptr, 0) == 0, "initialize first"); + Require(_sceFiberInitializeImpl_nid_postfix(g_second, "second", SecondEntry, 22, g_secondContext.data(), g_secondContext.size(), nullptr, 0) == 0, "initialize second"); + const auto threadCsr = _mm_getcsr(); + std::uint64_t returned = 0; + Require(sceFiberRun_nid_postfix(g_first, 100, &returned) == 0 && returned == 250, "run first until second returns"); + Require(_mm_getcsr() == threadCsr, "thread MXCSR restored after the fibers"); + std::thread([&] { + Require(sceFiberRun_nid_postfix(g_second, 260, &returned) == 0 && returned == 301, "resume second on another thread"); + }).join(); + Require(sceFiberFinalize(g_first) == 0, "finalize first"); + Require(sceFiberFinalize(g_second) == 0, "finalize second"); +}