[XThread] Replace guest exception handling with setjmp/longjmp

This commit is contained in:
Herman S.
2025-10-06 16:44:09 +09:00
parent dac9883473
commit 22f21fada9
2 changed files with 26 additions and 21 deletions

View File

@@ -513,16 +513,6 @@ X_STATUS XThread::Terminate(int exit_code) {
return X_STATUS_SUCCESS; return X_STATUS_SUCCESS;
} }
class reenter_exception {
public:
reenter_exception(uint32_t address) : address_(address) {};
virtual ~reenter_exception() {};
uint32_t address() const { return address_; }
private:
uint32_t address_;
};
void XThread::Execute() { void XThread::Execute() {
XELOGKERNEL("XThread::Execute thid {} (handle={:08X}, '{}', native={:08X})", XELOGKERNEL("XThread::Execute thid {} (handle={:08X}, '{}', native={:08X})",
thread_id_, handle(), thread_name_, thread_->system_id()); thread_id_, handle(), thread_name_, thread_->system_id());
@@ -551,25 +541,34 @@ void XThread::Execute() {
want_exit_code = true; want_exit_code = true;
} }
// Set up reentry jump buffer for fiber-based stack switching.
// When Reenter() is called (e.g., by KeSetCurrentStackPointers), it will
// longjmp back here instead of throwing an exception through JIT code.
uint32_t next_address; uint32_t next_address;
try { if (setjmp(reentry_jmp_buf_) != 0) {
// Longjmp returned here - reentry requested
next_address = reentry_address_;
} else {
// Initial execution
exit_code = static_cast<int>(kernel_state()->processor()->Execute( exit_code = static_cast<int>(kernel_state()->processor()->Execute(
thread_state_, address, args.data(), args.size())); thread_state_, address, args.data(), args.size()));
next_address = 0; next_address = 0;
} catch (const reenter_exception& ree) {
next_address = ree.address();
} }
// Handle reentry loop for fiber switching.
// See XThread::Reenter comments. // See XThread::Reenter comments.
while (next_address != 0) { while (next_address != 0) {
try { // Set up jump buffer for potential reentries during this execution
if (setjmp(reentry_jmp_buf_) != 0) {
// Nested reentry occurred
next_address = reentry_address_;
} else {
// Execute at the reentry address
kernel_state()->processor()->ExecuteRaw(thread_state_, next_address); kernel_state()->processor()->ExecuteRaw(thread_state_, next_address);
next_address = 0; next_address = 0;
if (want_exit_code) { if (want_exit_code) {
exit_code = static_cast<int>(thread_state_->context()->r[3]); exit_code = static_cast<int>(thread_state_->context()->r[3]);
} }
} catch (const reenter_exception& ree) {
next_address = ree.address();
} }
} }
@@ -579,11 +578,12 @@ void XThread::Execute() {
} }
void XThread::Reenter(uint32_t address) { void XThread::Reenter(uint32_t address) {
// TODO(gibbed): Maybe use setjmp/longjmp on Windows? // Use setjmp/longjmp instead of exceptions to avoid issues with unwinding
// https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/longjmp#remarks // through JIT-compiled code, which lacks exception handling metadata.
// On Windows with /EH, setjmp/longjmp do stack unwinding. // This is called when the game switches fiber stacks (e.g., via
// Is there a better solution than exceptions for stack unwinding? // KeSetCurrentStackPointers in games like Forza Horizon 2).
throw reenter_exception(address); reentry_address_ = address;
std::longjmp(reentry_jmp_buf_, 1);
} }
void XThread::EnterCriticalRegion() { void XThread::EnterCriticalRegion() {

View File

@@ -11,6 +11,7 @@
#define XENIA_KERNEL_XTHREAD_H_ #define XENIA_KERNEL_XTHREAD_H_
#include <atomic> #include <atomic>
#include <csetjmp>
#include <string> #include <string>
#include "xenia/base/mutex.h" #include "xenia/base/mutex.h"
@@ -466,6 +467,10 @@ class XThread : public XObject, public cpu::Thread {
int32_t priority_ = 0; int32_t priority_ = 0;
// Reentry context for setjmp/longjmp based stack unwinding
std::jmp_buf reentry_jmp_buf_;
uint32_t reentry_address_ = 0;
std::mutex thread_lock_; std::mutex thread_lock_;
}; };