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:
parent
b985d17f82
commit
ba9201874f
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
Loading…
Reference in New Issue