feat(fiber): implement fibers on linux

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ButHENCbXXt8h4EZuWUabJ
This commit is contained in:
Dean Galvin 2026-09-28 14:22:48 +13:00
parent b985d17f82
commit ba9201874f
3 changed files with 154 additions and 23 deletions

View File

@ -363,6 +363,14 @@ if(BUILD_TESTING)
set_tests_properties(guest_json PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceJson2>")
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:$<TARGET_FILE_DIR:libSceFiber>")
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)

View File

@ -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<void**>(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

81
core/libs/tests/Fiber.cpp Normal file
View File

@ -0,0 +1,81 @@
#include "SceTypes.hpp"
#include "prx/libc/include/general/VabiMacros.hpp"
#include <array>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <thread>
#include <xmmintrin.h>
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<std::int32_t>(0x80590005);
constexpr std::int32_t FiberErrorState = static_cast<std::int32_t>(0x80590006);
alignas(16) std::array<unsigned char, 64 * 1024> g_firstContext;
alignas(16) std::array<unsigned char, 64 * 1024> g_secondContext;
struct alignas(8) FiberStorage {
unsigned char bytes[0x100];
};
FiberStorage g_firstStorage;
FiberStorage g_secondStorage;
auto* const g_first = reinterpret_cast<FiberObject*>(&g_firstStorage);
auto* const g_second = reinterpret_cast<FiberObject*>(&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");
}