mirror of
https://github.com/PCSX2/pcsx2.git
synced 2025-04-02 10:52:54 -04:00
git-svn-id: http://pcsx2.googlecode.com/svn/branches/pcsx2_0.9.2@159 96395faa-99c1-11dd-bbfe-3dabce05a288
507 lines
12 KiB
C
507 lines
12 KiB
C
/*
|
|
* PCL by Davide Libenzi ( Portable Coroutine Library )
|
|
* Copyright (C) 2003 Davide Libenzi
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*
|
|
* Davide Libenzi <davidel@xmailserver.org>
|
|
*
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include "pcl_config.h"
|
|
#include "pcl.h"
|
|
|
|
#if defined(CO_USE_UCONEXT)
|
|
#include <ucontext.h>
|
|
|
|
typedef ucontext_t co_core_ctx_t;
|
|
#else
|
|
#include <setjmp.h>
|
|
|
|
typedef jmp_buf co_core_ctx_t;
|
|
#endif
|
|
|
|
#if defined(CO_USE_SIGCONTEXT)
|
|
#include <signal.h>
|
|
#endif
|
|
|
|
|
|
/*
|
|
* The following value must be power of two ( N^2 ).
|
|
*/
|
|
#define CO_STK_ALIGN 256
|
|
#define CO_STK_COROSIZE ((sizeof(coroutine) + CO_STK_ALIGN - 1) & ~(CO_STK_ALIGN - 1))
|
|
#define CO_MIN_SIZE (4 * 1024)
|
|
|
|
|
|
typedef struct s_co_ctx {
|
|
co_core_ctx_t cc;
|
|
} co_ctx_t;
|
|
|
|
typedef struct s_coroutine {
|
|
co_ctx_t ctx;
|
|
int alloc;
|
|
struct s_coroutine *caller;
|
|
struct s_coroutine *restarget;
|
|
void (*func)(void *);
|
|
void *data;
|
|
} coroutine;
|
|
|
|
|
|
static coroutine co_main;
|
|
static coroutine *co_curr = &co_main;
|
|
static coroutine *co_dhelper;
|
|
|
|
#if defined(CO_USE_SIGCONTEXT)
|
|
|
|
static volatile int ctx_called;
|
|
static co_ctx_t *ctx_creating;
|
|
static void *ctx_creating_func;
|
|
static sigset_t ctx_creating_sigs;
|
|
static co_ctx_t ctx_trampoline;
|
|
static co_ctx_t ctx_caller;
|
|
|
|
#endif /* #if defined(CO_USE_SIGCONTEXT) */
|
|
|
|
|
|
|
|
static int co_ctx_sdir(unsigned long psp) {
|
|
int nav = 0;
|
|
unsigned long csp = (unsigned long) &nav;
|
|
|
|
return psp > csp ? -1: +1;
|
|
}
|
|
|
|
|
|
static int co_ctx_stackdir(void) {
|
|
int cav = 0;
|
|
|
|
return co_ctx_sdir((unsigned long) &cav);
|
|
}
|
|
|
|
|
|
#if defined(CO_USE_UCONEXT)
|
|
|
|
static int co_set_context(co_ctx_t *ctx, void *func, char *stkbase, long stksiz) {
|
|
|
|
if (getcontext(&ctx->cc))
|
|
return -1;
|
|
|
|
ctx->cc.uc_link = NULL;
|
|
|
|
ctx->cc.uc_stack.ss_sp = stkbase;
|
|
ctx->cc.uc_stack.ss_size = stksiz - sizeof(long);
|
|
ctx->cc.uc_stack.ss_flags = 0;
|
|
|
|
makecontext(&ctx->cc, func, 1);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static void co_switch_context(co_ctx_t *octx, co_ctx_t *nctx) {
|
|
|
|
if (swapcontext(&octx->cc, &nctx->cc) < 0) {
|
|
fprintf(stderr, "[PCL] Context switch failed: curr=%p\n",
|
|
co_curr);
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
#else /* #if defined(CO_USE_UCONEXT) */
|
|
|
|
#if defined(CO_USE_SIGCONTEXT)
|
|
|
|
/*
|
|
* This code comes from the GNU Pth implementation and uses the
|
|
* sigstack/sigaltstack() trick.
|
|
*
|
|
* The ingenious fact is that this variant runs really on _all_ POSIX
|
|
* compliant systems without special platform kludges. But be _VERY_
|
|
* carefully when you change something in the following code. The slightest
|
|
* change or reordering can lead to horribly broken code. Really every
|
|
* function call in the following case is intended to be how it is, doubt
|
|
* me...
|
|
*
|
|
* For more details we strongly recommend you to read the companion
|
|
* paper ``Portable Multithreading -- The Signal Stack Trick for
|
|
* User-Space Thread Creation'' from Ralf S. Engelschall.
|
|
*/
|
|
|
|
static void co_ctx_bootstrap(void) {
|
|
co_ctx_t * volatile ctx_starting;
|
|
void (* volatile ctx_starting_func)(void);
|
|
|
|
/*
|
|
* Switch to the final signal mask (inherited from parent)
|
|
*/
|
|
sigprocmask(SIG_SETMASK, &ctx_creating_sigs, NULL);
|
|
|
|
/*
|
|
* Move startup details from static storage to local auto
|
|
* variables which is necessary because it has to survive in
|
|
* a local context until the thread is scheduled for real.
|
|
*/
|
|
ctx_starting = ctx_creating;
|
|
ctx_starting_func = (void (*)(void)) ctx_creating_func;
|
|
|
|
/*
|
|
* Save current machine state (on new stack) and
|
|
* go back to caller until we're scheduled for real...
|
|
*/
|
|
if (!setjmp(ctx_starting->cc))
|
|
longjmp(ctx_caller.cc, 1);
|
|
|
|
/*
|
|
* The new thread is now running: GREAT!
|
|
* Now we just invoke its init function....
|
|
*/
|
|
ctx_starting_func();
|
|
|
|
fprintf(stderr, "[PCL] Hmm, you really shouldn't reach this point: curr=%p\n",
|
|
co_curr);
|
|
exit(1);
|
|
}
|
|
|
|
|
|
static void co_ctx_trampoline(int sig) {
|
|
/*
|
|
* Save current machine state and _immediately_ go back with
|
|
* a standard "return" (to stop the signal handler situation)
|
|
* to let him remove the stack again. Notice that we really
|
|
* have do a normal "return" here, or the OS would consider
|
|
* the thread to be running on a signal stack which isn't
|
|
* good (for instance it wouldn't allow us to spawn a thread
|
|
* from within a thread, etc.)
|
|
*/
|
|
if (!setjmp(ctx_trampoline.cc)) {
|
|
ctx_called = 1;
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Ok, the caller has longjmp'ed back to us, so now prepare
|
|
* us for the real machine state switching. We have to jump
|
|
* into another function here to get a new stack context for
|
|
* the auto variables (which have to be auto-variables
|
|
* because the start of the thread happens later).
|
|
*/
|
|
co_ctx_bootstrap();
|
|
}
|
|
|
|
|
|
static int co_set_context(co_ctx_t *ctx, void *func, char *stkbase, long stksiz) {
|
|
struct sigaction sa;
|
|
struct sigaction osa;
|
|
sigset_t osigs;
|
|
sigset_t sigs;
|
|
#if defined(CO_HAS_SIGSTACK)
|
|
struct sigstack ss;
|
|
struct sigstack oss;
|
|
#elif defined(CO_HAS_SIGALTSTACK)
|
|
struct sigaltstack ss;
|
|
struct sigaltstack oss;
|
|
#else
|
|
#error "PCL: Unknown context stack type"
|
|
#endif
|
|
|
|
/*
|
|
* Preserve the SIGUSR1 signal state, block SIGUSR1,
|
|
* and establish our signal handler. The signal will
|
|
* later transfer control onto the signal stack.
|
|
*/
|
|
sigemptyset(&sigs);
|
|
sigaddset(&sigs, SIGUSR1);
|
|
sigprocmask(SIG_BLOCK, &sigs, &osigs);
|
|
sa.sa_handler = co_ctx_trampoline;
|
|
sigemptyset(&sa.sa_mask);
|
|
sa.sa_flags = SA_ONSTACK;
|
|
if (sigaction(SIGUSR1, &sa, &osa) != 0)
|
|
return -1;
|
|
|
|
/*
|
|
* Set the new stack.
|
|
*
|
|
* For sigaltstack we're lucky [from sigaltstack(2) on
|
|
* FreeBSD 3.1]: ``Signal stacks are automatically adjusted
|
|
* for the direction of stack growth and alignment
|
|
* requirements''
|
|
*
|
|
* For sigstack we have to decide ourself [from sigstack(2)
|
|
* on Solaris 2.6]: ``The direction of stack growth is not
|
|
* indicated in the historical definition of struct sigstack.
|
|
* The only way to portably establish a stack pointer is for
|
|
* the application to determine stack growth direction.''
|
|
*/
|
|
#if defined(CO_HAS_SIGALTSTACK)
|
|
ss.ss_sp = stkbase;
|
|
ss.ss_size = stksiz - sizeof(long);
|
|
ss.ss_flags = 0;
|
|
if (sigaltstack(&ss, &oss) < 0)
|
|
return -1;
|
|
#elif defined(CO_HAS_SIGSTACK)
|
|
if (co_ctx_stackdir() < 0)
|
|
ss.ss_sp = (stkbase + stksiz - sizeof(long));
|
|
else
|
|
ss.ss_sp = stkbase;
|
|
ss.ss_onstack = 0;
|
|
if (sigstack(&ss, &oss) < 0)
|
|
return -1;
|
|
#else
|
|
#error "PCL: Unknown context stack type"
|
|
#endif
|
|
|
|
/*
|
|
* Now transfer control onto the signal stack and set it up.
|
|
* It will return immediately via "return" after the setjmp()
|
|
* was performed. Be careful here with race conditions. The
|
|
* signal can be delivered the first time sigsuspend() is
|
|
* called.
|
|
*/
|
|
ctx_called = 0;
|
|
kill(getpid(), SIGUSR1);
|
|
sigfillset(&sigs);
|
|
sigdelset(&sigs, SIGUSR1);
|
|
while (!ctx_called)
|
|
sigsuspend(&sigs);
|
|
|
|
/*
|
|
* Inform the system that we are back off the signal stack by
|
|
* removing the alternative signal stack. Be careful here: It
|
|
* first has to be disabled, before it can be removed.
|
|
*/
|
|
#if defined(CO_HAS_SIGALTSTACK)
|
|
sigaltstack(NULL, &ss);
|
|
ss.ss_flags = SS_DISABLE;
|
|
if (sigaltstack(&ss, NULL) < 0)
|
|
return -1;
|
|
sigaltstack(NULL, &ss);
|
|
if (!(ss.ss_flags & SS_DISABLE))
|
|
return -1;
|
|
if (!(oss.ss_flags & SS_DISABLE))
|
|
sigaltstack(&oss, NULL);
|
|
#elif defined(CO_HAS_SIGSTACK)
|
|
if (sigstack(&oss, NULL))
|
|
return -1;
|
|
#else
|
|
#error "PCL: Unknown context stack type"
|
|
#endif
|
|
|
|
/*
|
|
* Restore the old SIGUSR1 signal handler and mask
|
|
*/
|
|
sigaction(SIGUSR1, &osa, NULL);
|
|
sigprocmask(SIG_SETMASK, &osigs, NULL);
|
|
|
|
/*
|
|
* Set creation information.
|
|
*/
|
|
ctx_creating = ctx;
|
|
ctx_creating_func = func;
|
|
memcpy(&ctx_creating_sigs, &osigs, sizeof(sigset_t));
|
|
|
|
/*
|
|
* Now enter the trampoline again, but this time not as a signal
|
|
* handler. Instead we jump into it directly.
|
|
*/
|
|
if (!setjmp(ctx_caller.cc))
|
|
longjmp(ctx_trampoline.cc, 1);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#else /* #if defined(CO_USE_SIGCONTEXT) */
|
|
|
|
static int co_set_context(co_ctx_t *ctx, void *func, char *stkbase, long stksiz) {
|
|
char *stack;
|
|
|
|
stack = stkbase + stksiz - sizeof(long);
|
|
|
|
setjmp(ctx->cc);
|
|
|
|
#if defined(__GLIBC__) && defined(__GLIBC_MINOR__) \
|
|
&& __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 0 && defined(JB_PC) && defined(JB_SP)
|
|
ctx->cc[0].__jmpbuf[JB_PC] = (int) func;
|
|
ctx->cc[0].__jmpbuf[JB_SP] = (int) stack;
|
|
#elif defined(__GLIBC__) && defined(__GLIBC_MINOR__) \
|
|
&& __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 0 && defined(__mc68000__)
|
|
ctx->cc[0].__jmpbuf[0].__aregs[0] = (long) func;
|
|
ctx->cc[0].__jmpbuf[0].__sp = (int *) stack;
|
|
#elif defined(__GNU_LIBRARY__) && defined(__i386__)
|
|
ctx->cc[0].__jmpbuf[0].__pc = func;
|
|
ctx->cc[0].__jmpbuf[0].__sp = stack;
|
|
#elif defined(_WIN32) && defined(_MSC_VER)
|
|
((_JUMP_BUFFER *) &ctx->cc)->Eip = (long) func;
|
|
((_JUMP_BUFFER *) &ctx->cc)->Esp = (long) stack;
|
|
#elif defined(__GLIBC__) && defined(__GLIBC_MINOR__) \
|
|
&& __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 0 && (defined(__powerpc64__) || defined(__powerpc__))
|
|
ctx->cc[0].__jmpbuf[JB_LR] = (int) func;
|
|
ctx->cc[0].__jmpbuf[JB_GPR1] = (int) stack;
|
|
#else
|
|
|
|
// automatically assume windows/x86 (cygwin)
|
|
typedef struct __JUMP_BUFFER {
|
|
unsigned long Ebp;
|
|
unsigned long Ebx;
|
|
unsigned long Edi;
|
|
unsigned long Esi;
|
|
unsigned long Esp;
|
|
unsigned long Eip;
|
|
unsigned long Registration;
|
|
unsigned long TryLevel;
|
|
unsigned long Cookie;
|
|
unsigned long UnwindFunc;
|
|
unsigned long UnwindData[6];
|
|
} _JUMP_BUFFER;
|
|
|
|
((_JUMP_BUFFER *) &ctx->cc)->Eip = (long) func;
|
|
((_JUMP_BUFFER *) &ctx->cc)->Esp = (long) stack;
|
|
#warning "PCL: Unsupported setjmp/longjmp platform. Please report to <davidel@xmailserver.org>"
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
#endif /* #if defined(CO_USE_SIGCONTEXT) */
|
|
|
|
|
|
static void co_switch_context(co_ctx_t *octx, co_ctx_t *nctx) {
|
|
|
|
if (!setjmp(octx->cc))
|
|
longjmp(nctx->cc, 1);
|
|
}
|
|
|
|
#endif /* #if defined(CO_USE_UCONEXT) */
|
|
|
|
|
|
static void co_runner(void) {
|
|
coroutine *co = co_curr;
|
|
|
|
co->restarget = co->caller;
|
|
co->func(co->data);
|
|
co_exit();
|
|
}
|
|
|
|
|
|
coroutine_t co_create(void (*func)(void *), void *data, void *stack, int size) {
|
|
int alloc = 0, r = CO_STK_COROSIZE;
|
|
coroutine *co;
|
|
|
|
if ((size &= ~(sizeof(long) - 1)) < CO_MIN_SIZE)
|
|
return NULL;
|
|
if (!stack) {
|
|
size = (size + sizeof(coroutine) + CO_STK_ALIGN - 1) & ~(CO_STK_ALIGN - 1);
|
|
stack = malloc(size);
|
|
if (!stack)
|
|
return NULL;
|
|
alloc = size;
|
|
}
|
|
co = stack;
|
|
stack = (char *) stack + CO_STK_COROSIZE;
|
|
co->alloc = alloc;
|
|
co->func = func;
|
|
co->data = data;
|
|
if (co_set_context(&co->ctx, co_runner, stack, size - CO_STK_COROSIZE) < 0) {
|
|
if (alloc)
|
|
free(co);
|
|
return NULL;
|
|
}
|
|
|
|
return (coroutine_t) co;
|
|
}
|
|
|
|
|
|
void co_delete(coroutine_t coro) {
|
|
coroutine *co = (coroutine *) coro;
|
|
|
|
if (co == co_curr) {
|
|
fprintf(stderr, "[PCL] Cannot delete itself: curr=%p\n",
|
|
co_curr);
|
|
exit(1);
|
|
}
|
|
if (co->alloc)
|
|
free(co);
|
|
}
|
|
|
|
|
|
void co_call(coroutine_t coro) {
|
|
coroutine *co = (coroutine *) coro, *oldco = co_curr;
|
|
|
|
co->caller = co_curr;
|
|
co_curr = co;
|
|
|
|
co_switch_context(&oldco->ctx, &co->ctx);
|
|
}
|
|
|
|
|
|
void co_resume(void) {
|
|
|
|
co_call(co_curr->restarget);
|
|
co_curr->restarget = co_curr->caller;
|
|
}
|
|
|
|
|
|
static void co_del_helper(void *data) {
|
|
coroutine *cdh;
|
|
|
|
for (;;) {
|
|
cdh = co_dhelper;
|
|
co_dhelper = NULL;
|
|
co_delete(co_curr->caller);
|
|
co_call((coroutine_t) cdh);
|
|
if (!co_dhelper) {
|
|
fprintf(stderr, "[PCL] Resume to delete helper coroutine: curr=%p\n",
|
|
co_curr);
|
|
exit(1);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void co_exit_to(coroutine_t coro) {
|
|
coroutine *co = (coroutine *) coro;
|
|
static coroutine *dchelper = NULL;
|
|
static char stk[CO_MIN_SIZE];
|
|
|
|
if (!dchelper &&
|
|
!(dchelper = co_create(co_del_helper, NULL, stk, sizeof(stk)))) {
|
|
fprintf(stderr, "[PCL] Unable to create delete helper coroutine: curr=%p\n",
|
|
co_curr);
|
|
exit(1);
|
|
}
|
|
|
|
co_dhelper = co;
|
|
|
|
co_call((coroutine_t) dchelper);
|
|
|
|
fprintf(stderr, "[PCL] Stale coroutine called: curr=%p\n",
|
|
co_curr);
|
|
exit(1);
|
|
}
|
|
|
|
|
|
void co_exit(void) {
|
|
|
|
co_exit_to((coroutine_t) co_curr->restarget);
|
|
}
|
|
|
|
|
|
coroutine_t co_current(void) {
|
|
|
|
return (coroutine_t) co_curr;
|
|
}
|
|
|