mirror of
https://github.com/panda3d/panda3d.git
synced 2025-10-04 10:54:24 -04:00
experiment with WIN_THREAD_CONTEXT
This commit is contained in:
parent
33276672f3
commit
15d98ead96
@ -85,6 +85,8 @@
|
|||||||
conditionVarSimpleImpl.cxx \
|
conditionVarSimpleImpl.cxx \
|
||||||
conditionVarSpinlockImpl.cxx \
|
conditionVarSpinlockImpl.cxx \
|
||||||
config_pipeline.cxx \
|
config_pipeline.cxx \
|
||||||
|
contextSwitch_longjmp_src.c contextSwitch_ucontext_src.c \
|
||||||
|
contextSwitch_windows_src.c \
|
||||||
cycleData.cxx \
|
cycleData.cxx \
|
||||||
cycleDataLockedReader.cxx \
|
cycleDataLockedReader.cxx \
|
||||||
cycleDataLockedStageReader.cxx \
|
cycleDataLockedStageReader.cxx \
|
||||||
|
@ -17,418 +17,26 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
#ifdef THREAD_SIMPLE_IMPL
|
#if defined(THREAD_SIMPLE_IMPL) && !defined(CPPPARSER)
|
||||||
|
|
||||||
#if defined(PHAVE_UCONTEXT_H)
|
#ifdef WIN32
|
||||||
|
/* Define this macro to use native Windows threading constructs to
|
||||||
|
switch contexts. */
|
||||||
|
#define WIN_THREAD_CONTEXT
|
||||||
|
#endif
|
||||||
|
|
||||||
#else /* PHAVE_UCONTEXT_H */
|
#if defined(WIN_THREAD_CONTEXT)
|
||||||
|
|
||||||
|
#include "contextSwitch_windows_src.c"
|
||||||
|
|
||||||
|
#elif defined(PHAVE_UCONTEXT_H)
|
||||||
|
|
||||||
|
#include "contextSwitch_ucontext_src.c"
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
#include "contextSwitch_longjmp_src.c"
|
||||||
|
|
||||||
#endif /* PHAVE_UCONTEXT_H */
|
#endif /* PHAVE_UCONTEXT_H */
|
||||||
|
|
||||||
#if defined(PHAVE_UCONTEXT_H)
|
|
||||||
|
|
||||||
/* We'd prefer to use getcontext() / setcontext() to portably change
|
|
||||||
execution contexts within C code. That's what these library
|
|
||||||
functions are designed for. */
|
|
||||||
#ifdef __APPLE__
|
|
||||||
#include <sys/ucontext.h>
|
|
||||||
#else
|
|
||||||
#include <ucontext.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
struct ThreadContext {
|
|
||||||
ucontext_t _ucontext;
|
|
||||||
#ifdef __APPLE__
|
|
||||||
// Due to a bug in OSX 10.5, the system ucontext_t declaration
|
|
||||||
// doesn't reserve enough space, and we need to reserve some
|
|
||||||
// additional space to make room.
|
|
||||||
#define EXTRA_PADDING_SIZE 4096
|
|
||||||
char _extra_padding[EXTRA_PADDING_SIZE];
|
|
||||||
#endif
|
|
||||||
};
|
|
||||||
|
|
||||||
static void
|
|
||||||
begin_context(ContextFunction *thread_func, void *data) {
|
|
||||||
(*thread_func)(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
init_thread_context(struct ThreadContext *context,
|
|
||||||
unsigned char *stack, size_t stack_size,
|
|
||||||
ContextFunction *thread_func, void *data) {
|
|
||||||
if (getcontext(&context->_ucontext) != 0) {
|
|
||||||
fprintf(stderr, "getcontext failed in init_thread_context!\n");
|
|
||||||
// Too bad for you.
|
|
||||||
abort();
|
|
||||||
}
|
|
||||||
|
|
||||||
context->_ucontext.uc_stack.ss_sp = stack;
|
|
||||||
context->_ucontext.uc_stack.ss_size = stack_size;
|
|
||||||
context->_ucontext.uc_stack.ss_flags = 0;
|
|
||||||
context->_ucontext.uc_link = NULL;
|
|
||||||
|
|
||||||
makecontext(&context->_ucontext, (void (*)())&begin_context, 2, thread_func, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
save_thread_context(struct ThreadContext *context,
|
|
||||||
ContextFunction *next_context, void *data) {
|
|
||||||
/* getcontext requires us to use a volatile auto variable to
|
|
||||||
differentiate between pass 1 (immediate return) and pass 2
|
|
||||||
(return from setcontext). */
|
|
||||||
volatile int context_return = 0;
|
|
||||||
|
|
||||||
if (getcontext(&context->_ucontext) != 0) {
|
|
||||||
fprintf(stderr, "getcontext failed!\n");
|
|
||||||
// Nothing to do here.
|
|
||||||
abort();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (context_return) {
|
|
||||||
/* We have just returned from setcontext. In this case, return
|
|
||||||
from the function. The stack is still good. */
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
context_return = 1;
|
|
||||||
|
|
||||||
/* We are still in the calling thread. In this case, we cannot
|
|
||||||
return from the function without damaging the stack. Insted,
|
|
||||||
call next_context() and trust the caller to call
|
|
||||||
switch_to_thread_context() in there somewhere. */
|
|
||||||
|
|
||||||
(*next_context)(data);
|
|
||||||
|
|
||||||
/* We shouldn't get here. */
|
|
||||||
abort();
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
switch_to_thread_context(struct ThreadContext *context) {
|
|
||||||
setcontext(&context->_ucontext);
|
|
||||||
|
|
||||||
/* Shouldn't get here. */
|
|
||||||
abort();
|
|
||||||
}
|
|
||||||
|
|
||||||
#else
|
|
||||||
|
|
||||||
/* Unfortunately, setcontext() is not defined everywhere (even though
|
|
||||||
it claims to be adopted by Posix). So we have to fall back to
|
|
||||||
setjmp() / longjmp() in its absence. This is a hackier solution. */
|
|
||||||
|
|
||||||
#if defined(_M_IX86) || defined(__i386__)
|
|
||||||
/* Maybe we can implement our own setjmp/longjmp in assembly code.
|
|
||||||
This will be safer than the system version, since who knows what
|
|
||||||
that one's really doing? */
|
|
||||||
|
|
||||||
typedef int cs_jmp_buf[33];
|
|
||||||
|
|
||||||
#define CS_JB_SP 4
|
|
||||||
|
|
||||||
#else
|
|
||||||
|
|
||||||
/* Fall back to the system implmentation of setjmp/longjmp. */
|
|
||||||
#include <setjmp.h>
|
|
||||||
|
|
||||||
typedef jmp_buf cs_jmp_buf;
|
|
||||||
#define cs_setjmp setjmp
|
|
||||||
#define cs_longjmp(buf) longjmp(buf, 1)
|
|
||||||
|
|
||||||
#ifdef JB_SP
|
|
||||||
#define CS_JB_SP JB_SP
|
|
||||||
|
|
||||||
#elif defined(__ppc__)
|
|
||||||
/* This was determined experimentally through test_setjmp. */
|
|
||||||
#define CS_JB_SP 0
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif /* __i386__ */
|
|
||||||
|
|
||||||
struct ThreadContext {
|
|
||||||
cs_jmp_buf _jmp_context;
|
|
||||||
};
|
|
||||||
|
|
||||||
/* The approach is: hack our way onto the new stack pointer right now,
|
|
||||||
then call setjmp() to record that stack pointer in the
|
|
||||||
_jmp_context. Then restore back to the original stack pointer. */
|
|
||||||
|
|
||||||
#if defined(_M_IX86)
|
|
||||||
/* Here is our own implementation of setjmp and longjmp for I386, via
|
|
||||||
Windows syntax. */
|
|
||||||
|
|
||||||
/* warning C4731: frame pointer register 'ebp' modified by inline assembly code */
|
|
||||||
#pragma warning(disable:4731)
|
|
||||||
|
|
||||||
int
|
|
||||||
cs_setjmp(cs_jmp_buf env) {
|
|
||||||
__asm {
|
|
||||||
pop ebp; /* Restore the frame pointer that the compiler pushed */
|
|
||||||
|
|
||||||
pop edx; /* edx = return address */
|
|
||||||
pop eax; /* eax = &env */
|
|
||||||
push eax; /* keep &env on the stack; the caller will remove it */
|
|
||||||
|
|
||||||
mov [eax + 0], ebx;
|
|
||||||
mov [eax + 4], edi;
|
|
||||||
mov [eax + 8], esi;
|
|
||||||
mov [eax + 12], ebp;
|
|
||||||
mov [eax + 16], esp;
|
|
||||||
mov [eax + 20], edx;
|
|
||||||
|
|
||||||
fnsave [eax + 24]; /* save floating-point state */
|
|
||||||
|
|
||||||
xor eax,eax; /* return 0: pass 1 return */
|
|
||||||
jmp edx; /* this works like ret */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
cs_longjmp(cs_jmp_buf env) {
|
|
||||||
_asm {
|
|
||||||
mov eax, env;
|
|
||||||
|
|
||||||
mov ebx, [eax + 0];
|
|
||||||
mov edi, [eax + 4];
|
|
||||||
mov esi, [eax + 8];
|
|
||||||
mov ebp, [eax + 12];
|
|
||||||
mov esp, [eax + 16];
|
|
||||||
mov edx, [eax + 20];
|
|
||||||
|
|
||||||
frstor [eax + 24]; /* restore floating-point state */
|
|
||||||
|
|
||||||
mov eax, 1; /* return 1 from setjmp: pass 2 return */
|
|
||||||
jmp edx; /* return from above setjmp call */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#elif defined(__i386__)
|
|
||||||
/* Here is our own implementation of setjmp and longjmp for I386, via
|
|
||||||
GNU syntax. */
|
|
||||||
|
|
||||||
#if defined(IS_LINUX)
|
|
||||||
/* On Linux, the leading underscores are not implicitly added for C
|
|
||||||
function names. */
|
|
||||||
#define cs_setjmp _cs_setjmp
|
|
||||||
#define cs_longjmp _cs_longjmp
|
|
||||||
#endif
|
|
||||||
|
|
||||||
int cs_setjmp(cs_jmp_buf env);
|
|
||||||
void cs_longjmp(cs_jmp_buf env);
|
|
||||||
|
|
||||||
__asm__
|
|
||||||
("_cs_setjmp:\n"
|
|
||||||
"popl %edx\n"
|
|
||||||
"popl %eax\n"
|
|
||||||
"pushl %eax\n"
|
|
||||||
|
|
||||||
"movl %ebx, 0(%eax)\n"
|
|
||||||
"movl %edi, 4(%eax)\n"
|
|
||||||
"movl %esi, 8(%eax)\n"
|
|
||||||
"movl %ebp, 12(%eax)\n"
|
|
||||||
"movl %esp, 16(%eax)\n"
|
|
||||||
"movl %edx, 20(%eax)\n"
|
|
||||||
|
|
||||||
"fnsave 24(%eax)\n"
|
|
||||||
|
|
||||||
"xorl %eax, %eax\n"
|
|
||||||
"jmp *%edx\n");
|
|
||||||
|
|
||||||
__asm__
|
|
||||||
("_cs_longjmp:\n"
|
|
||||||
"popl %edx\n"
|
|
||||||
"popl %eax\n"
|
|
||||||
|
|
||||||
"movl 0(%eax), %ebx\n"
|
|
||||||
"movl 4(%eax), %edi\n"
|
|
||||||
"movl 8(%eax), %esi\n"
|
|
||||||
"movl 12(%eax), %ebp\n"
|
|
||||||
"movl 16(%eax), %esp\n"
|
|
||||||
"movl 20(%eax), %edx\n"
|
|
||||||
|
|
||||||
"frstor 24(%eax)\n"
|
|
||||||
|
|
||||||
"mov $1,%eax\n"
|
|
||||||
"jmp *%edx\n");
|
|
||||||
|
|
||||||
#endif /* __i386__ */
|
|
||||||
|
|
||||||
/* Ideally, including setjmp.h would have defined JB_SP, which will
|
|
||||||
tell us where in the context structure we can muck with the stack
|
|
||||||
pointer. If it didn't define this symbol, we have to guess it. */
|
|
||||||
#ifndef CS_JB_SP
|
|
||||||
|
|
||||||
#if defined(IS_OSX) && defined(__i386__)
|
|
||||||
/* We have determined this value empirically, via test_setjmp.cxx in
|
|
||||||
this directory. */
|
|
||||||
#define CS_JB_SP 9
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif /* CS_JB_SP */
|
|
||||||
|
|
||||||
static struct ThreadContext *st_context;
|
|
||||||
static unsigned char *st_stack;
|
|
||||||
static size_t st_stack_size;
|
|
||||||
static ContextFunction *st_thread_func;
|
|
||||||
static void *st_data;
|
|
||||||
|
|
||||||
static cs_jmp_buf orig_stack;
|
|
||||||
|
|
||||||
/* We can't declare this function static--gcc might want to inline it
|
|
||||||
in that case, and then the code crashes. I hope this doesn't mean
|
|
||||||
that the stack is still not getting restored correctly in the above
|
|
||||||
assembly code. */
|
|
||||||
void
|
|
||||||
setup_context_2(void) {
|
|
||||||
/* Here we are running on the new stack. Copy the key data onto our
|
|
||||||
new stack. */
|
|
||||||
ContextFunction *volatile thread_func = st_thread_func;
|
|
||||||
void *volatile data = st_data;
|
|
||||||
|
|
||||||
if (cs_setjmp(st_context->_jmp_context) == 0) {
|
|
||||||
/* The _jmp_context is set up and ready to run. Now restore the
|
|
||||||
original stack and return. We can't simply return from this
|
|
||||||
function, since it might overwrite some of the stack data on
|
|
||||||
the way out. */
|
|
||||||
cs_longjmp(orig_stack);
|
|
||||||
|
|
||||||
/* Shouldn't get here. */
|
|
||||||
abort();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* We come here the first time the thread starts. */
|
|
||||||
(*thread_func)(data);
|
|
||||||
|
|
||||||
/* We shouldn't get here, since we don't expect the thread_func to
|
|
||||||
return. */
|
|
||||||
abort();
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
setup_context_1(void) {
|
|
||||||
/* Save the current stack frame so we can return to it (at the end
|
|
||||||
of setup_context_2()). */
|
|
||||||
if (cs_setjmp(orig_stack) == 0) {
|
|
||||||
/* First, switch to the new stack. Save the current context using
|
|
||||||
setjmp(). This saves out all of the processor register values,
|
|
||||||
though it doesn't muck with the stack. */
|
|
||||||
static cs_jmp_buf temp;
|
|
||||||
if (cs_setjmp(temp) == 0) {
|
|
||||||
/* This is the initial return from setjmp. Still the original
|
|
||||||
stack. */
|
|
||||||
|
|
||||||
/* Now we overwrite the stack pointer value in the saved
|
|
||||||
register context. This doesn't work with all implementations
|
|
||||||
of setjmp/longjmp. */
|
|
||||||
|
|
||||||
/* We give ourselves a small buffer of unused space at the top
|
|
||||||
of the stack, to allow for the stack frame and such that this
|
|
||||||
code might be assuming is there. */
|
|
||||||
(*(void **)&temp[CS_JB_SP]) = (st_stack + st_stack_size - 0x100);
|
|
||||||
|
|
||||||
/* And finally, we place ourselves on the new stack by using
|
|
||||||
longjmp() to reload the modified context. */
|
|
||||||
cs_longjmp(temp);
|
|
||||||
|
|
||||||
/* Shouldn't get here. */
|
|
||||||
abort();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* This is the second return from setjmp. Now we're on the new
|
|
||||||
stack. */
|
|
||||||
setup_context_2();
|
|
||||||
|
|
||||||
/* Shouldn't get here. */
|
|
||||||
abort();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* By now we are back to the original stack. */
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
init_thread_context(struct ThreadContext *context,
|
|
||||||
unsigned char *stack, size_t stack_size,
|
|
||||||
ContextFunction *thread_func, void *data) {
|
|
||||||
/* Copy all of the input parameters to static variables, then begin
|
|
||||||
the stack-switching process. */
|
|
||||||
st_context = context;
|
|
||||||
st_stack = stack;
|
|
||||||
st_stack_size = stack_size;
|
|
||||||
st_thread_func = thread_func;
|
|
||||||
st_data = data;
|
|
||||||
|
|
||||||
setup_context_1();
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
save_thread_context(struct ThreadContext *context,
|
|
||||||
ContextFunction *next_context, void *data) {
|
|
||||||
if (cs_setjmp(context->_jmp_context) != 0) {
|
|
||||||
/* We have just returned from longjmp. In this case, return from
|
|
||||||
the function. The stack is still good. */
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* We are still in the calling thread. In this case, we cannot
|
|
||||||
return from the function without damaging the stack. Insted,
|
|
||||||
call next_context() and trust the caller to call
|
|
||||||
switch_to_thread_context() in there somewhere. */
|
|
||||||
|
|
||||||
(*next_context)(data);
|
|
||||||
|
|
||||||
/* We shouldn't get here. */
|
|
||||||
abort();
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
switch_to_thread_context(struct ThreadContext *context) {
|
|
||||||
cs_longjmp(context->_jmp_context);
|
|
||||||
|
|
||||||
/* Shouldn't get here. */
|
|
||||||
abort();
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif /* PHAVE_UCONTEXT_H */
|
|
||||||
|
|
||||||
struct ThreadContext *
|
|
||||||
alloc_thread_context() {
|
|
||||||
struct ThreadContext *context =
|
|
||||||
(struct ThreadContext *)malloc(sizeof(struct ThreadContext));
|
|
||||||
|
|
||||||
#if defined(__APPLE__) && defined(_DEBUG)
|
|
||||||
{
|
|
||||||
int p;
|
|
||||||
// Pre-fill the extra_padding with bytes that we can recognize
|
|
||||||
// later.
|
|
||||||
for (p = 0; p < EXTRA_PADDING_SIZE; ++p) {
|
|
||||||
context->_extra_padding[p] = (p & 0xff);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif // __APPLE__
|
|
||||||
|
|
||||||
return context;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
free_thread_context(struct ThreadContext *context) {
|
|
||||||
#if defined(__APPLE__) && defined(_DEBUG)
|
|
||||||
{
|
|
||||||
// Because of the OSX 10.5 bug, we anticipate that the extra_padding
|
|
||||||
// may have been filled in with junk. Confirm this.
|
|
||||||
int p = EXTRA_PADDING_SIZE;
|
|
||||||
while (p > 0) {
|
|
||||||
--p;
|
|
||||||
if (context->_extra_padding[p] != (char)(p & 0xff)) {
|
|
||||||
fprintf(stderr, "Context was mangled at byte %d: %d!\n", p, context->_extra_padding[p]);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif // __APPLE__
|
|
||||||
free(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif /* THREAD_SIMPLE_IMPL */
|
#endif /* THREAD_SIMPLE_IMPL */
|
||||||
|
@ -36,16 +36,21 @@ struct ThreadContext;
|
|||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
typedef void ContextFunction(void *);
|
typedef void ContextFunction(struct ThreadContext *from_context, void *);
|
||||||
|
typedef void ThreadFunction(void *);
|
||||||
|
|
||||||
/* Call this to fill in the appropriate values in context. The stack
|
extern const int needs_stack_prealloc;
|
||||||
must already have been allocated. The context will be initialized
|
|
||||||
so that when switch_to_thread_context() is called, it will begin
|
/* Call this to fill in the appropriate values in context. If
|
||||||
executing thread_func(data), which should not return. This function
|
needs_stack_prealloc (above) is true, the stack must already have
|
||||||
will return normally. */
|
been allocated; if needs_stack_prealloc is false, the stack pointer
|
||||||
|
is not used and may be NULL. The context will be initialized so
|
||||||
|
that when switch_to_thread_context() is called, it will begin
|
||||||
|
executing thread_func(data), which should not return. This
|
||||||
|
function will return normally. */
|
||||||
void init_thread_context(struct ThreadContext *context,
|
void init_thread_context(struct ThreadContext *context,
|
||||||
unsigned char *stack, size_t stack_size,
|
unsigned char *stack, size_t stack_size,
|
||||||
ContextFunction *thread_func, void *data);
|
ThreadFunction *thread_func, void *data);
|
||||||
|
|
||||||
/* Call this to save the current thread context. This function does
|
/* Call this to save the current thread context. This function does
|
||||||
not return until switch_to_thread_context() is called. Instead it
|
not return until switch_to_thread_context() is called. Instead it
|
||||||
@ -53,15 +58,19 @@ void init_thread_context(struct ThreadContext *context,
|
|||||||
void save_thread_context(struct ThreadContext *context,
|
void save_thread_context(struct ThreadContext *context,
|
||||||
ContextFunction *next_context, void *data);
|
ContextFunction *next_context, void *data);
|
||||||
|
|
||||||
/* Call this to resume executing a previously saved context. When
|
/* Call this to resume executing a previously saved context.
|
||||||
called, it will return from save_thread_context() in the saved
|
from_context must be the currently-executing context, and
|
||||||
stack (or begin executing thread_func()). */
|
to_context is the context to resume. When called, it will return
|
||||||
void switch_to_thread_context(struct ThreadContext *context);
|
from save_thread_context() in the saved stack (or begin executing
|
||||||
|
thread_func()). */
|
||||||
|
void switch_to_thread_context(struct ThreadContext *from_context,
|
||||||
|
struct ThreadContext *to_context);
|
||||||
|
|
||||||
/* Use this pair of functions to transparently allocate and destroy an
|
/* Use this pair of functions to transparently allocate and destroy an
|
||||||
opaque ThreadContext object of the appropriate size. These
|
opaque ThreadContext object of the appropriate size. These
|
||||||
functions only allocate memory; they do not initialize the values
|
functions allocate memory, and initialize the context as
|
||||||
of the context (see init_thread_context(), above, for that). */
|
appropriate for the main thread. See init_main_context() to finish
|
||||||
|
the initialization for a new thread. */
|
||||||
struct ThreadContext *alloc_thread_context();
|
struct ThreadContext *alloc_thread_context();
|
||||||
void free_thread_context(struct ThreadContext *context);
|
void free_thread_context(struct ThreadContext *context);
|
||||||
|
|
||||||
|
308
panda/src/pipeline/contextSwitch_longjmp_src.c
Executable file
308
panda/src/pipeline/contextSwitch_longjmp_src.c
Executable file
@ -0,0 +1,308 @@
|
|||||||
|
/* Filename: contextSwitch_longjmp_src.c
|
||||||
|
* Created by: drose (15Apr10)
|
||||||
|
*
|
||||||
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||||
|
*
|
||||||
|
* PANDA 3D SOFTWARE
|
||||||
|
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||||
|
*
|
||||||
|
* All use of this software is subject to the terms of the revised BSD
|
||||||
|
* license. You should have received a copy of this license along
|
||||||
|
* with this source code in a file named "LICENSE."
|
||||||
|
*
|
||||||
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||||
|
|
||||||
|
/* This is the implementation of user-space context switching using
|
||||||
|
setmp() / longjmp(). This is the hackier implementation,
|
||||||
|
which is necessary if setcontext() is not available. */
|
||||||
|
|
||||||
|
const int needs_stack_prealloc = 1;
|
||||||
|
|
||||||
|
#if defined(_M_IX86) || defined(__i386__)
|
||||||
|
/* Maybe we can implement our own setjmp/longjmp in assembly code.
|
||||||
|
This will be safer than the system version, since who knows what
|
||||||
|
that one's really doing? */
|
||||||
|
|
||||||
|
typedef int cs_jmp_buf[33];
|
||||||
|
|
||||||
|
#define CS_JB_SP 4
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
/* Fall back to the system implementation of setjmp/longjmp. */
|
||||||
|
#include <setjmp.h>
|
||||||
|
|
||||||
|
typedef jmp_buf cs_jmp_buf;
|
||||||
|
#define cs_setjmp setjmp
|
||||||
|
#define cs_longjmp(buf) longjmp(buf, 1)
|
||||||
|
|
||||||
|
#ifdef JB_SP
|
||||||
|
#define CS_JB_SP JB_SP
|
||||||
|
|
||||||
|
#elif defined(__ppc__)
|
||||||
|
/* This was determined experimentally through test_setjmp. */
|
||||||
|
#define CS_JB_SP 0
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* __i386__ */
|
||||||
|
|
||||||
|
struct ThreadContext {
|
||||||
|
cs_jmp_buf _jmp_context;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* The approach is: hack our way onto the new stack pointer right now,
|
||||||
|
then call setjmp() to record that stack pointer in the
|
||||||
|
_jmp_context. Then restore back to the original stack pointer. */
|
||||||
|
|
||||||
|
#if defined(_M_IX86)
|
||||||
|
/* Here is our own implementation of setjmp and longjmp for I386, via
|
||||||
|
Windows syntax. */
|
||||||
|
|
||||||
|
/* warning C4731: frame pointer register 'ebp' modified by inline assembly code */
|
||||||
|
#pragma warning(disable:4731)
|
||||||
|
|
||||||
|
int
|
||||||
|
cs_setjmp(cs_jmp_buf env) {
|
||||||
|
__asm {
|
||||||
|
pop ebp; /* Restore the frame pointer that the compiler pushed */
|
||||||
|
|
||||||
|
pop edx; /* edx = return address */
|
||||||
|
pop eax; /* eax = &env */
|
||||||
|
push eax; /* keep &env on the stack; the caller will remove it */
|
||||||
|
|
||||||
|
mov [eax + 0], ebx;
|
||||||
|
mov [eax + 4], edi;
|
||||||
|
mov [eax + 8], esi;
|
||||||
|
mov [eax + 12], ebp;
|
||||||
|
mov [eax + 16], esp;
|
||||||
|
mov [eax + 20], edx;
|
||||||
|
|
||||||
|
fnsave [eax + 24]; /* save floating-point state */
|
||||||
|
|
||||||
|
xor eax,eax; /* return 0: pass 1 return */
|
||||||
|
jmp edx; /* this works like ret */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
cs_longjmp(cs_jmp_buf env) {
|
||||||
|
_asm {
|
||||||
|
mov eax, env;
|
||||||
|
|
||||||
|
mov ebx, [eax + 0];
|
||||||
|
mov edi, [eax + 4];
|
||||||
|
mov esi, [eax + 8];
|
||||||
|
mov ebp, [eax + 12];
|
||||||
|
mov esp, [eax + 16];
|
||||||
|
mov edx, [eax + 20];
|
||||||
|
|
||||||
|
frstor [eax + 24]; /* restore floating-point state */
|
||||||
|
|
||||||
|
mov eax, 1; /* return 1 from setjmp: pass 2 return */
|
||||||
|
jmp edx; /* return from above setjmp call */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#elif defined(__i386__)
|
||||||
|
/* Here is our own implementation of setjmp and longjmp for I386, via
|
||||||
|
GNU syntax. */
|
||||||
|
|
||||||
|
#if defined(IS_LINUX)
|
||||||
|
/* On Linux, the leading underscores are not implicitly added for C
|
||||||
|
function names. */
|
||||||
|
#define cs_setjmp _cs_setjmp
|
||||||
|
#define cs_longjmp _cs_longjmp
|
||||||
|
#endif
|
||||||
|
|
||||||
|
int cs_setjmp(cs_jmp_buf env);
|
||||||
|
void cs_longjmp(cs_jmp_buf env);
|
||||||
|
|
||||||
|
__asm__
|
||||||
|
("_cs_setjmp:\n"
|
||||||
|
"popl %edx\n"
|
||||||
|
"popl %eax\n"
|
||||||
|
"pushl %eax\n"
|
||||||
|
|
||||||
|
"movl %ebx, 0(%eax)\n"
|
||||||
|
"movl %edi, 4(%eax)\n"
|
||||||
|
"movl %esi, 8(%eax)\n"
|
||||||
|
"movl %ebp, 12(%eax)\n"
|
||||||
|
"movl %esp, 16(%eax)\n"
|
||||||
|
"movl %edx, 20(%eax)\n"
|
||||||
|
|
||||||
|
"fnsave 24(%eax)\n"
|
||||||
|
|
||||||
|
"xorl %eax, %eax\n"
|
||||||
|
"jmp *%edx\n");
|
||||||
|
|
||||||
|
__asm__
|
||||||
|
("_cs_longjmp:\n"
|
||||||
|
"popl %edx\n"
|
||||||
|
"popl %eax\n"
|
||||||
|
|
||||||
|
"movl 0(%eax), %ebx\n"
|
||||||
|
"movl 4(%eax), %edi\n"
|
||||||
|
"movl 8(%eax), %esi\n"
|
||||||
|
"movl 12(%eax), %ebp\n"
|
||||||
|
"movl 16(%eax), %esp\n"
|
||||||
|
"movl 20(%eax), %edx\n"
|
||||||
|
|
||||||
|
"frstor 24(%eax)\n"
|
||||||
|
|
||||||
|
"mov $1,%eax\n"
|
||||||
|
"jmp *%edx\n");
|
||||||
|
|
||||||
|
#endif /* __i386__ */
|
||||||
|
|
||||||
|
/* Ideally, including setjmp.h would have defined JB_SP, which will
|
||||||
|
tell us where in the context structure we can muck with the stack
|
||||||
|
pointer. If it didn't define this symbol, we have to guess it. */
|
||||||
|
#ifndef CS_JB_SP
|
||||||
|
|
||||||
|
#if defined(IS_OSX) && defined(__i386__)
|
||||||
|
/* We have determined this value empirically, via test_setjmp.cxx in
|
||||||
|
this directory. */
|
||||||
|
#define CS_JB_SP 9
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* CS_JB_SP */
|
||||||
|
|
||||||
|
static struct ThreadContext *st_context;
|
||||||
|
static unsigned char *st_stack;
|
||||||
|
static size_t st_stack_size;
|
||||||
|
static ThreadFunction *st_thread_func;
|
||||||
|
static void *st_data;
|
||||||
|
|
||||||
|
static cs_jmp_buf orig_stack;
|
||||||
|
|
||||||
|
/* We can't declare this function static--gcc might want to inline it
|
||||||
|
in that case, and then the code crashes. I hope this doesn't mean
|
||||||
|
that the stack is still not getting restored correctly in the above
|
||||||
|
assembly code. */
|
||||||
|
void
|
||||||
|
setup_context_2(void) {
|
||||||
|
/* Here we are running on the new stack. Copy the key data onto our
|
||||||
|
new stack. */
|
||||||
|
ThreadFunction *volatile thread_func = st_thread_func;
|
||||||
|
void *volatile data = st_data;
|
||||||
|
|
||||||
|
if (cs_setjmp(st_context->_jmp_context) == 0) {
|
||||||
|
/* The _jmp_context is set up and ready to run. Now restore the
|
||||||
|
original stack and return. We can't simply return from this
|
||||||
|
function, since it might overwrite some of the stack data on
|
||||||
|
the way out. */
|
||||||
|
cs_longjmp(orig_stack);
|
||||||
|
|
||||||
|
/* Shouldn't get here. */
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* We come here the first time the thread starts. */
|
||||||
|
(*thread_func)(data);
|
||||||
|
|
||||||
|
/* We shouldn't get here, since we don't expect the thread_func to
|
||||||
|
return. */
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
setup_context_1(void) {
|
||||||
|
/* Save the current stack frame so we can return to it (at the end
|
||||||
|
of setup_context_2()). */
|
||||||
|
if (cs_setjmp(orig_stack) == 0) {
|
||||||
|
/* First, switch to the new stack. Save the current context using
|
||||||
|
setjmp(). This saves out all of the processor register values,
|
||||||
|
though it doesn't muck with the stack. */
|
||||||
|
static cs_jmp_buf temp;
|
||||||
|
if (cs_setjmp(temp) == 0) {
|
||||||
|
/* This is the initial return from setjmp. Still the original
|
||||||
|
stack. */
|
||||||
|
|
||||||
|
/* Now we overwrite the stack pointer value in the saved
|
||||||
|
register context. This doesn't work with all implementations
|
||||||
|
of setjmp/longjmp. */
|
||||||
|
|
||||||
|
/* We give ourselves a small buffer of unused space at the top
|
||||||
|
of the stack, to allow for the stack frame and such that this
|
||||||
|
code might be assuming is there. */
|
||||||
|
(*(void **)&temp[CS_JB_SP]) = (st_stack + st_stack_size - 0x100);
|
||||||
|
|
||||||
|
/* And finally, we place ourselves on the new stack by using
|
||||||
|
longjmp() to reload the modified context. */
|
||||||
|
cs_longjmp(temp);
|
||||||
|
|
||||||
|
/* Shouldn't get here. */
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* This is the second return from setjmp. Now we're on the new
|
||||||
|
stack. */
|
||||||
|
setup_context_2();
|
||||||
|
|
||||||
|
/* Shouldn't get here. */
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* By now we are back to the original stack. */
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
init_thread_context(struct ThreadContext *context,
|
||||||
|
unsigned char *stack, size_t stack_size,
|
||||||
|
ThreadFunction *thread_func, void *data) {
|
||||||
|
/* Copy all of the input parameters to static variables, then begin
|
||||||
|
the stack-switching process. */
|
||||||
|
st_context = context;
|
||||||
|
st_stack = stack;
|
||||||
|
st_stack_size = stack_size;
|
||||||
|
st_thread_func = thread_func;
|
||||||
|
st_data = data;
|
||||||
|
|
||||||
|
setup_context_1();
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
save_thread_context(struct ThreadContext *context,
|
||||||
|
ContextFunction *next_context, void *data) {
|
||||||
|
if (cs_setjmp(context->_jmp_context) != 0) {
|
||||||
|
/* We have just returned from longjmp. In this case, return from
|
||||||
|
the function. The stack is still good. */
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* We are still in the calling thread. In this case, we cannot
|
||||||
|
return from the function without damaging the stack. Insted,
|
||||||
|
call next_context() and trust the caller to call
|
||||||
|
switch_to_thread_context() in there somewhere. */
|
||||||
|
|
||||||
|
(*next_context)(context, data);
|
||||||
|
|
||||||
|
/* We shouldn't get here. */
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
switch_to_thread_context(struct ThreadContext *from_context,
|
||||||
|
struct ThreadContext *to_context) {
|
||||||
|
cs_longjmp(to_context->_jmp_context);
|
||||||
|
|
||||||
|
/* Shouldn't get here. */
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ThreadContext *
|
||||||
|
alloc_thread_context() {
|
||||||
|
struct ThreadContext *context =
|
||||||
|
(struct ThreadContext *)malloc(sizeof(struct ThreadContext));
|
||||||
|
memset(context, 0, sizeof(struct ThreadContext));
|
||||||
|
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
free_thread_context(struct ThreadContext *context) {
|
||||||
|
free(context);
|
||||||
|
}
|
141
panda/src/pipeline/contextSwitch_ucontext_src.c
Executable file
141
panda/src/pipeline/contextSwitch_ucontext_src.c
Executable file
@ -0,0 +1,141 @@
|
|||||||
|
/* Filename: contextSwitch_ucontext_src.c
|
||||||
|
* Created by: drose (15Apr10)
|
||||||
|
*
|
||||||
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||||
|
*
|
||||||
|
* PANDA 3D SOFTWARE
|
||||||
|
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||||
|
*
|
||||||
|
* All use of this software is subject to the terms of the revised BSD
|
||||||
|
* license. You should have received a copy of this license along
|
||||||
|
* with this source code in a file named "LICENSE."
|
||||||
|
*
|
||||||
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||||
|
|
||||||
|
/* This is the implementation of user-space context switching using
|
||||||
|
getcontext() / setcontext(). This is the preferred implementation,
|
||||||
|
if these library functions are available; that's what they are
|
||||||
|
designed for. */
|
||||||
|
|
||||||
|
#ifdef __APPLE__
|
||||||
|
#include <sys/ucontext.h>
|
||||||
|
#else
|
||||||
|
#include <ucontext.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
const int needs_stack_prealloc = 1;
|
||||||
|
|
||||||
|
struct ThreadContext {
|
||||||
|
ucontext_t _ucontext;
|
||||||
|
#ifdef __APPLE__
|
||||||
|
// Due to a bug in OSX 10.5, the system ucontext_t declaration
|
||||||
|
// doesn't reserve enough space, and we need to reserve some
|
||||||
|
// additional space to make room.
|
||||||
|
#define EXTRA_PADDING_SIZE 4096
|
||||||
|
char _extra_padding[EXTRA_PADDING_SIZE];
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
static void
|
||||||
|
begin_context(ThreadFunction *thread_func, void *data) {
|
||||||
|
(*thread_func)(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
init_thread_context(struct ThreadContext *context,
|
||||||
|
unsigned char *stack, size_t stack_size,
|
||||||
|
ThreadFunction *thread_func, void *data) {
|
||||||
|
if (getcontext(&context->_ucontext) != 0) {
|
||||||
|
fprintf(stderr, "getcontext failed in init_thread_context!\n");
|
||||||
|
// Too bad for you.
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
context->_ucontext.uc_stack.ss_sp = stack;
|
||||||
|
context->_ucontext.uc_stack.ss_size = stack_size;
|
||||||
|
context->_ucontext.uc_stack.ss_flags = 0;
|
||||||
|
context->_ucontext.uc_link = NULL;
|
||||||
|
|
||||||
|
makecontext(&context->_ucontext, (void (*)())&begin_context, 2, thread_func, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
save_thread_context(struct ThreadContext *context,
|
||||||
|
ContextFunction *next_context, void *data) {
|
||||||
|
/* getcontext requires us to use a volatile auto variable to
|
||||||
|
differentiate between pass 1 (immediate return) and pass 2
|
||||||
|
(return from setcontext). */
|
||||||
|
volatile int context_return = 0;
|
||||||
|
|
||||||
|
if (getcontext(&context->_ucontext) != 0) {
|
||||||
|
fprintf(stderr, "getcontext failed!\n");
|
||||||
|
// Nothing to do here.
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context_return) {
|
||||||
|
/* We have just returned from setcontext. In this case, return
|
||||||
|
from the function. The stack is still good. */
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
context_return = 1;
|
||||||
|
|
||||||
|
/* We are still in the calling thread. In this case, we cannot
|
||||||
|
return from the function without damaging the stack. Insted,
|
||||||
|
call next_context() and trust the caller to call
|
||||||
|
switch_to_thread_context() in there somewhere. */
|
||||||
|
|
||||||
|
(*next_context)(context, data);
|
||||||
|
|
||||||
|
/* We shouldn't get here. */
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
switch_to_thread_context(struct ThreadContext *from_context,
|
||||||
|
struct ThreadContext *to_context) {
|
||||||
|
setcontext(&to_context->_ucontext);
|
||||||
|
|
||||||
|
/* Shouldn't get here. */
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ThreadContext *
|
||||||
|
alloc_thread_context() {
|
||||||
|
struct ThreadContext *context =
|
||||||
|
(struct ThreadContext *)malloc(sizeof(struct ThreadContext));
|
||||||
|
memset(context, 0, sizeof(struct ThreadContext));
|
||||||
|
|
||||||
|
#if defined(__APPLE__) && defined(_DEBUG)
|
||||||
|
{
|
||||||
|
int p;
|
||||||
|
// Pre-fill the extra_padding with bytes that we can recognize
|
||||||
|
// later.
|
||||||
|
for (p = 0; p < EXTRA_PADDING_SIZE; ++p) {
|
||||||
|
context->_extra_padding[p] = (p & 0xff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif // __APPLE__
|
||||||
|
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
free_thread_context(struct ThreadContext *context) {
|
||||||
|
#if defined(__APPLE__) && defined(_DEBUG)
|
||||||
|
{
|
||||||
|
// Because of the OSX 10.5 bug, we anticipate that the extra_padding
|
||||||
|
// may have been filled in with junk. Confirm this.
|
||||||
|
int p = EXTRA_PADDING_SIZE;
|
||||||
|
while (p > 0) {
|
||||||
|
--p;
|
||||||
|
if (context->_extra_padding[p] != (char)(p & 0xff)) {
|
||||||
|
fprintf(stderr, "Context was mangled at byte %d: %d!\n", p, context->_extra_padding[p]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif // __APPLE__
|
||||||
|
free(context);
|
||||||
|
}
|
153
panda/src/pipeline/contextSwitch_windows_src.c
Executable file
153
panda/src/pipeline/contextSwitch_windows_src.c
Executable file
@ -0,0 +1,153 @@
|
|||||||
|
/* Filename: contextSwitch_windows_src.c
|
||||||
|
* Created by: drose (15Apr10)
|
||||||
|
*
|
||||||
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||||
|
*
|
||||||
|
* PANDA 3D SOFTWARE
|
||||||
|
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||||
|
*
|
||||||
|
* All use of this software is subject to the terms of the revised BSD
|
||||||
|
* license. You should have received a copy of this license along
|
||||||
|
* with this source code in a file named "LICENSE."
|
||||||
|
*
|
||||||
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||||
|
|
||||||
|
/* This is the implementation of user-space context switching using
|
||||||
|
native Windows threading constructs to manage the different
|
||||||
|
execution contexts. This isn't strictly user-space, since we use
|
||||||
|
OS threading constructs, but we use a global lock to ensure that
|
||||||
|
only one thread at a time is active. Thus, we still don't have to
|
||||||
|
defend the code against critical sections globally, so we still get
|
||||||
|
the low-overhead benefit of SIMPLE_THREADS; this is just a simple,
|
||||||
|
reliable way to manage context switches. */
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
#include <setjmp.h>
|
||||||
|
|
||||||
|
/* The Windows implementation doesn't use the stack pointer. */
|
||||||
|
const int needs_stack_prealloc = 0;
|
||||||
|
|
||||||
|
static struct ThreadContext *current_context = NULL;
|
||||||
|
|
||||||
|
struct ThreadContext {
|
||||||
|
/* Each context is really its own thread. */
|
||||||
|
HANDLE _thread;
|
||||||
|
|
||||||
|
/* This event is in the signaled state when the thread is ready to
|
||||||
|
roll. */
|
||||||
|
HANDLE _ready;
|
||||||
|
|
||||||
|
// This is set FALSE while the thread is alive, and TRUE if the
|
||||||
|
// thread is to be terminated when it next wakes up.
|
||||||
|
int _terminated;
|
||||||
|
|
||||||
|
/* These are preloaded with the startup parameters, then cleared to
|
||||||
|
NULL for subsequent runs. */
|
||||||
|
ThreadFunction *_thread_func;
|
||||||
|
void *_data;
|
||||||
|
|
||||||
|
/* We use setjmp()/longjmp() to manage the detail of returning from
|
||||||
|
save_thread_context() when we call switch_to_thread_context(). */
|
||||||
|
jmp_buf _jmp_context;
|
||||||
|
};
|
||||||
|
|
||||||
|
static DWORD WINAPI
|
||||||
|
thread_main(LPVOID data) {
|
||||||
|
struct ThreadContext *context = (struct ThreadContext *)data;
|
||||||
|
|
||||||
|
// Wait for the thread to be awoken.
|
||||||
|
WaitForSingleObject(context->_ready, INFINITE);
|
||||||
|
|
||||||
|
if (context->_terminated) {
|
||||||
|
/* We've been rudely terminated. Exit gracefully. */
|
||||||
|
ExitThread(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now we can begin.
|
||||||
|
(*context->_thread_func)(context->_data);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
init_thread_context(struct ThreadContext *context,
|
||||||
|
unsigned char *stack, size_t stack_size,
|
||||||
|
ThreadFunction *thread_func, void *data) {
|
||||||
|
context->_thread_func = thread_func;
|
||||||
|
context->_data = data;
|
||||||
|
|
||||||
|
context->_thread = CreateThread(NULL, stack_size,
|
||||||
|
thread_main, context, 0, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
save_thread_context(struct ThreadContext *context,
|
||||||
|
ContextFunction *next_context, void *data) {
|
||||||
|
/* Save the current context so we can return here when the thread is
|
||||||
|
awoken. */
|
||||||
|
if (setjmp(context->_jmp_context) != 0) {
|
||||||
|
/* We have just returned from longjmp. In this case, return from
|
||||||
|
the function. */
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
current_context = context;
|
||||||
|
(*next_context)(context, data);
|
||||||
|
|
||||||
|
/* Should not get here. */
|
||||||
|
assert(FALSE);
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
switch_to_thread_context(struct ThreadContext *from_context,
|
||||||
|
struct ThreadContext *to_context) {
|
||||||
|
/* Pause the current thread, and switch to the indicated context.
|
||||||
|
This function should not return. */
|
||||||
|
assert(from_context == current_context);
|
||||||
|
|
||||||
|
/* Wake up the target thread. */
|
||||||
|
SetEvent(to_context->_ready);
|
||||||
|
|
||||||
|
/* And now put the from thread to sleep until it is again awoken. */
|
||||||
|
WaitForSingleObject(from_context->_ready, INFINITE);
|
||||||
|
|
||||||
|
if (from_context->_terminated) {
|
||||||
|
/* We've been rudely terminated. Exit gracefully. */
|
||||||
|
ExitThread(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Now we have been signaled again, and we're ready to resume the
|
||||||
|
thread. */
|
||||||
|
longjmp(from_context->_jmp_context, 1);
|
||||||
|
|
||||||
|
/* Should not get here. */
|
||||||
|
assert(FALSE);
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ThreadContext *
|
||||||
|
alloc_thread_context() {
|
||||||
|
struct ThreadContext *context =
|
||||||
|
(struct ThreadContext *)malloc(sizeof(struct ThreadContext));
|
||||||
|
|
||||||
|
memset(context, 0, sizeof(struct ThreadContext));
|
||||||
|
context->_ready = CreateEvent(NULL, FALSE, FALSE, NULL);
|
||||||
|
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
free_thread_context(struct ThreadContext *context) {
|
||||||
|
/* Make sure the thread wakes and exits gracefully. */
|
||||||
|
context->_terminated = TRUE;
|
||||||
|
SetEvent(context->_ready);
|
||||||
|
WaitForSingleObject(context->_thread, INFINITE);
|
||||||
|
|
||||||
|
CloseHandle(context->_ready);
|
||||||
|
if (context->_thread != NULL) {
|
||||||
|
CloseHandle(context->_thread);
|
||||||
|
}
|
||||||
|
|
||||||
|
free(context);
|
||||||
|
}
|
@ -23,6 +23,24 @@ get_current_thread() {
|
|||||||
return ThreadSimpleManager::get_global_ptr()->get_current_thread()->_parent_obj;
|
return ThreadSimpleManager::get_global_ptr()->get_current_thread()->_parent_obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
// Function: ThreadSimpleImpl::is_same_system_thread
|
||||||
|
// Access: Public
|
||||||
|
// Description: Returns true if we are still running within the same
|
||||||
|
// OS-level thread that this thread begin in, or false
|
||||||
|
// if this appears to be running in a different thread.
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
INLINE bool ThreadSimpleImpl::
|
||||||
|
is_same_system_thread() const {
|
||||||
|
#ifdef HAVE_POSIX_THREADS
|
||||||
|
return pthread_equal(_posix_system_thread_id, pthread_self());
|
||||||
|
#endif
|
||||||
|
#ifdef WIN32
|
||||||
|
return (_win32_system_thread_id == GetCurrentThreadId());
|
||||||
|
#endif
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////
|
||||||
// Function: ThreadSimpleImpl::bind_thread
|
// Function: ThreadSimpleImpl::bind_thread
|
||||||
// Access: Public, Static
|
// Access: Public, Static
|
||||||
|
@ -51,6 +51,13 @@ ThreadSimpleImpl(Thread *parent_obj) :
|
|||||||
|
|
||||||
// Save this pointer for convenience.
|
// Save this pointer for convenience.
|
||||||
_manager = ThreadSimpleManager::get_global_ptr();
|
_manager = ThreadSimpleManager::get_global_ptr();
|
||||||
|
|
||||||
|
#ifdef HAVE_POSIX_THREADS
|
||||||
|
_posix_system_thread_id = -1;
|
||||||
|
#endif
|
||||||
|
#ifdef WIN32
|
||||||
|
_win32_system_thread_id = 0;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////
|
||||||
@ -87,6 +94,13 @@ setup_main_thread() {
|
|||||||
_priority = TP_normal;
|
_priority = TP_normal;
|
||||||
_priority_weight = _manager->_simple_thread_normal_weight;
|
_priority_weight = _manager->_simple_thread_normal_weight;
|
||||||
|
|
||||||
|
#ifdef HAVE_POSIX_THREADS
|
||||||
|
_posix_system_thread_id = pthread_self();
|
||||||
|
#endif
|
||||||
|
#ifdef WIN32
|
||||||
|
_win32_system_thread_id = GetCurrentThreadId();
|
||||||
|
#endif
|
||||||
|
|
||||||
_manager->set_current_thread(this);
|
_manager->set_current_thread(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,7 +119,9 @@ start(ThreadPriority priority, bool joinable) {
|
|||||||
|
|
||||||
nassertr(_stack == NULL, false);
|
nassertr(_stack == NULL, false);
|
||||||
_stack_size = memory_hook->round_up_to_page_size((size_t)thread_stack_size);
|
_stack_size = memory_hook->round_up_to_page_size((size_t)thread_stack_size);
|
||||||
_stack = (unsigned char *)memory_hook->mmap_alloc(_stack_size, true);
|
if (needs_stack_prealloc) {
|
||||||
|
_stack = (unsigned char *)memory_hook->mmap_alloc(_stack_size, true);
|
||||||
|
}
|
||||||
|
|
||||||
_joinable = joinable;
|
_joinable = joinable;
|
||||||
_status = TS_running;
|
_status = TS_running;
|
||||||
@ -254,6 +270,13 @@ begin_thread() {
|
|||||||
PyThreadState_Swap(_python_state);
|
PyThreadState_Swap(_python_state);
|
||||||
#endif // HAVE_PYTHON
|
#endif // HAVE_PYTHON
|
||||||
|
|
||||||
|
#ifdef HAVE_POSIX_THREADS
|
||||||
|
_posix_system_thread_id = pthread_self();
|
||||||
|
#endif
|
||||||
|
#ifdef WIN32
|
||||||
|
_win32_system_thread_id = GetCurrentThreadId();
|
||||||
|
#endif
|
||||||
|
|
||||||
// Here we are executing within the thread. Run the thread_main
|
// Here we are executing within the thread. Run the thread_main
|
||||||
// function defined for this thread.
|
// function defined for this thread.
|
||||||
_parent_obj->thread_main();
|
_parent_obj->thread_main();
|
||||||
|
@ -73,6 +73,8 @@ public:
|
|||||||
static void prepare_for_exit();
|
static void prepare_for_exit();
|
||||||
|
|
||||||
INLINE static Thread *get_current_thread();
|
INLINE static Thread *get_current_thread();
|
||||||
|
INLINE bool is_same_system_thread() const;
|
||||||
|
|
||||||
INLINE static void bind_thread(Thread *thread);
|
INLINE static void bind_thread(Thread *thread);
|
||||||
INLINE static bool is_threading_supported();
|
INLINE static bool is_threading_supported();
|
||||||
INLINE static bool is_true_threads();
|
INLINE static bool is_true_threads();
|
||||||
@ -142,6 +144,20 @@ private:
|
|||||||
ThreadSimpleManager *_manager;
|
ThreadSimpleManager *_manager;
|
||||||
static ThreadSimpleImpl *volatile _st_this;
|
static ThreadSimpleImpl *volatile _st_this;
|
||||||
|
|
||||||
|
// We may not mix-and-match OS threads with Panda's SIMPLE_THREADS.
|
||||||
|
// If we ever get a Panda context switch request from a different OS
|
||||||
|
// thread than the thread we think we should be in, that's a serious
|
||||||
|
// error that may cause major consequences. For this reason, we
|
||||||
|
// store the OS thread's current thread ID here when the thread is
|
||||||
|
// constructed, and insist that it never changes during the lifetime
|
||||||
|
// of the thread.
|
||||||
|
#ifdef HAVE_POSIX_THREADS
|
||||||
|
pthread_t _posix_system_thread_id;
|
||||||
|
#endif
|
||||||
|
#ifdef WIN32
|
||||||
|
DWORD _win32_system_thread_id;
|
||||||
|
#endif
|
||||||
|
|
||||||
friend class ThreadSimpleManager;
|
friend class ThreadSimpleManager;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -27,19 +27,13 @@ get_current_thread() {
|
|||||||
// Function: ThreadSimpleManager::is_same_system_thread
|
// Function: ThreadSimpleManager::is_same_system_thread
|
||||||
// Access: Public
|
// Access: Public
|
||||||
// Description: Returns true if we are still running within the same
|
// Description: Returns true if we are still running within the same
|
||||||
// OS-level thread that created the ThreadSimpleManager,
|
// OS-level thread we think we should be running in, or
|
||||||
// or false if this appears to be running in a different
|
// false if this appears to be running in a different
|
||||||
// thread.
|
// thread.
|
||||||
////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////
|
||||||
INLINE bool ThreadSimpleManager::
|
INLINE bool ThreadSimpleManager::
|
||||||
is_same_system_thread() const {
|
is_same_system_thread() const {
|
||||||
#ifdef HAVE_POSIX_THREADS
|
return _current_thread->is_same_system_thread();
|
||||||
return pthread_equal(_posix_system_thread_id, pthread_self());
|
|
||||||
#endif
|
|
||||||
#ifdef WIN32
|
|
||||||
return (_win32_system_thread_id == GetCurrentThreadId());
|
|
||||||
#endif
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////
|
||||||
|
@ -78,13 +78,6 @@ ThreadSimpleManager() :
|
|||||||
_clock = TrueClock::get_global_ptr();
|
_clock = TrueClock::get_global_ptr();
|
||||||
_waiting_for_exit = NULL;
|
_waiting_for_exit = NULL;
|
||||||
|
|
||||||
#ifdef HAVE_POSIX_THREADS
|
|
||||||
_posix_system_thread_id = pthread_self();
|
|
||||||
#endif
|
|
||||||
#ifdef WIN32
|
|
||||||
_win32_system_thread_id = GetCurrentThreadId();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Install these global pointers so very low-level code (code
|
// Install these global pointers so very low-level code (code
|
||||||
// defined before the pipeline directory) can yield when necessary.
|
// defined before the pipeline directory) can yield when necessary.
|
||||||
global_thread_yield = &Thread::force_yield;
|
global_thread_yield = &Thread::force_yield;
|
||||||
@ -542,9 +535,9 @@ init_pointers() {
|
|||||||
// of next_context().
|
// of next_context().
|
||||||
////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////
|
||||||
void ThreadSimpleManager::
|
void ThreadSimpleManager::
|
||||||
st_choose_next_context(void *data) {
|
st_choose_next_context(struct ThreadContext *from_context, void *data) {
|
||||||
ThreadSimpleManager *self = (ThreadSimpleManager *)data;
|
ThreadSimpleManager *self = (ThreadSimpleManager *)data;
|
||||||
self->choose_next_context();
|
self->choose_next_context(from_context);
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////
|
||||||
@ -554,7 +547,7 @@ st_choose_next_context(void *data) {
|
|||||||
// of next_context().
|
// of next_context().
|
||||||
////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////
|
||||||
void ThreadSimpleManager::
|
void ThreadSimpleManager::
|
||||||
choose_next_context() {
|
choose_next_context(struct ThreadContext *from_context) {
|
||||||
double now = get_current_time();
|
double now = get_current_time();
|
||||||
|
|
||||||
do_timeslice_accounting(_current_thread, now);
|
do_timeslice_accounting(_current_thread, now);
|
||||||
@ -696,7 +689,7 @@ choose_next_context() {
|
|||||||
<< " blocked, " << _sleeping.size() << " sleeping)\n";
|
<< " blocked, " << _sleeping.size() << " sleeping)\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
switch_to_thread_context(_current_thread->_context);
|
switch_to_thread_context(from_context, _current_thread->_context);
|
||||||
|
|
||||||
// Shouldn't get here.
|
// Shouldn't get here.
|
||||||
nassertv(false);
|
nassertv(false);
|
||||||
|
@ -38,6 +38,7 @@
|
|||||||
class Thread;
|
class Thread;
|
||||||
class ThreadSimpleImpl;
|
class ThreadSimpleImpl;
|
||||||
class BlockerSimple;
|
class BlockerSimple;
|
||||||
|
struct ThreadContext;
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////
|
||||||
// Class : ThreadSimpleManager
|
// Class : ThreadSimpleManager
|
||||||
@ -87,8 +88,8 @@ private:
|
|||||||
typedef pdeque<ThreadSimpleImpl *> FifoThreads;
|
typedef pdeque<ThreadSimpleImpl *> FifoThreads;
|
||||||
typedef pvector<ThreadSimpleImpl *> Sleeping;
|
typedef pvector<ThreadSimpleImpl *> Sleeping;
|
||||||
|
|
||||||
static void st_choose_next_context(void *data);
|
static void st_choose_next_context(struct ThreadContext *from_context, void *data);
|
||||||
void choose_next_context();
|
void choose_next_context(struct ThreadContext *from_context);
|
||||||
void do_timeslice_accounting(ThreadSimpleImpl *thread, double now);
|
void do_timeslice_accounting(ThreadSimpleImpl *thread, double now);
|
||||||
void wake_sleepers(Sleeping &sleepers, double now);
|
void wake_sleepers(Sleeping &sleepers, double now);
|
||||||
void wake_all_sleepers(Sleeping &sleepers);
|
void wake_all_sleepers(Sleeping &sleepers);
|
||||||
@ -159,19 +160,6 @@ private:
|
|||||||
TickRecords _tick_records;
|
TickRecords _tick_records;
|
||||||
unsigned int _total_ticks;
|
unsigned int _total_ticks;
|
||||||
|
|
||||||
// We may not mix-and-match OS threads with Panda's SIMPLE_THREADS.
|
|
||||||
// If we ever get a Panda context switch request from a different OS
|
|
||||||
// thread than the original thread, that's a serious error that may
|
|
||||||
// cause major consequences. For this reason, we store the OS
|
|
||||||
// thread's current thread ID here when the manager is constructed,
|
|
||||||
// and insist that it never changes.
|
|
||||||
#ifdef HAVE_POSIX_THREADS
|
|
||||||
pthread_t _posix_system_thread_id;
|
|
||||||
#endif
|
|
||||||
#ifdef WIN32
|
|
||||||
DWORD _win32_system_thread_id;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static bool _pointers_initialized;
|
static bool _pointers_initialized;
|
||||||
static ThreadSimpleManager *_global_ptr;
|
static ThreadSimpleManager *_global_ptr;
|
||||||
};
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user