move setjmp etc. into contextSwitch.c

This commit is contained in:
David Rose 2007-06-22 00:59:51 +00:00
parent 69766d382d
commit 03ef026f09
7 changed files with 307 additions and 206 deletions

View File

@ -11,7 +11,7 @@
#define COMBINED_SOURCES $[TARGET]_composite1.cxx $[TARGET]_composite2.cxx
#define SOURCES \
threadSimpleImpl_no_opt_.cxx \
contextSwitch.c contextSwitch.h \
blockerSimple.h blockerSimple.I \
conditionVar.h conditionVar.I \
conditionVarDebug.h conditionVarDebug.I \
@ -110,6 +110,7 @@
threadWin32Impl.cxx
#define INSTALL_HEADERS \
contextSwitch.h \
blockerSimple.h blockerSimple.I \
conditionVar.h conditionVar.I \
conditionVarDebug.h conditionVarDebug.I \

View File

@ -0,0 +1,166 @@
/* Filename: contextSwitch.c
* Created by: drose (21Jun07)
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* PANDA 3D SOFTWARE
* Copyright (c) 2001 - 2004, Disney Enterprises, Inc. All rights reserved
*
* All use of this software is subject to the terms of the Panda 3d
* Software license. You should have received a copy of this license
* along with this source code; you will also find a current copy of
* the license at http://etc.cmu.edu/panda3d/docs/license/ .
*
* To contact the maintainers of this program write to
* panda3d-general@lists.sourceforge.net .
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "contextSwitch.h"
#include <stdlib.h>
#include <stdio.h>
#ifdef THREAD_SIMPLE_IMPL
#ifdef HAVE_UCONTEXT_H
/* The getcontext() / setcontext() implementation. Easy-peasy. */
static void *
begin_context(ContextFunction *main_func, void *data) {
(*main_func)(data);
return data;
}
void
init_thread_context(struct ThreadContext *context,
unsigned char *stack, size_t stack_size,
ContextFunction *main_func, void *data) {
getcontext(&context->_ucontext);
_ucontext.uc_stack.ss_sp = stack;
_ucontext.uc_stack.ss_size = stack_size;
_ucontext.uc_stack.ss_flags = 0;
_ucontext.uc_link = NULL;
makecontext(&context->_ucontext, begin_context, 2, main_func, this);
}
#else
/* The setjmp() / longjmp() implementation. A bit hackier. */
/* 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. */
/* 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 JB_SP
#if defined(IS_OSX) && defined(__i386__)
/* We have determined this value empirically, via test_setjmp.cxx in
this directory. */
#define JB_SP 9
#elif defined(WIN32)
/* We have determined this value empirically, via test_setjmp.cxx in
this directory. */
#define JB_SP 4
#endif
#endif /* JB_SP */
static struct ThreadContext *st_context;
static unsigned char *st_stack;
static size_t st_stack_size;
static ContextFunction *st_main_func;
static void *st_data;
static jmp_buf orig_stack;
static void
setup_context_2(void) {
/* Here we are running on the new stack. Copy the key data onto our
new stack. */
ContextFunction *volatile main_func = st_main_func;
void *volatile data = st_data;
if (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. */
longjmp(orig_stack, 1);
/* Shouldn't get here. */
abort();
}
/* We come here the first time the thread starts. */
(*main_func)(data);
/* We shouldn't get here, since we don't expect the main_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 (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 jmp_buf temp;
if (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. */
(*(void **)&temp[JB_SP]) = (st_stack + st_stack_size);
/* And finally, we place ourselves on the new stack by using
longjmp() to reload the modified context. */
longjmp(temp, 1);
/* 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 *main_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_main_func = main_func;
st_data = data;
setup_context_1();
}
#endif /* HAVE_UCONTEXT_H */
#endif /* THREAD_SIMPLE_IMPL */

View File

@ -0,0 +1,103 @@
/* Filename: contextSwitch.h
* Created by: drose (21Jun07)
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* PANDA 3D SOFTWARE
* Copyright (c) 2001 - 2004, Disney Enterprises, Inc. All rights reserved
*
* All use of this software is subject to the terms of the Panda 3d
* Software license. You should have received a copy of this license
* along with this source code; you will also find a current copy of
* the license at http://etc.cmu.edu/panda3d/docs/license/ .
*
* To contact the maintainers of this program write to
* panda3d-general@lists.sourceforge.net .
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef CONTEXTSWITCH_H
#define CONTEXTSWITCH_H
#include "pandabase.h"
#include "selectThreadImpl.h"
/* This file defines the code to perform fundamental context switches
between different threads of execution within user space code. It
does this by saving and restoring the register state, including
transferring to a new stack. */
/* The code in this file is all written in C code, rather than C++, to
reduce possible conflicts from longjmp implementations that attempt
to be smart with respect to C++ destructors and exception
handling. */
#ifdef THREAD_SIMPLE_IMPL
#ifdef HAVE_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. */
#include <ucontext.h>
struct ThreadContext {
ucontext_t _ucontext;
};
#define save_thread_context(switched, context) \
{ \
volatile int context_return = 0; \
getcontext(&context->_ucontext); \
if (context_return) { \
(*(switched)) = 1; \
} \
context_return = 1; \
(*(switched)) = 0; \
}
#define switch_to_thread_context(context) \
setcontext(&(context)->_ucontext)
#else /* HAVE_UCONTEXT_H */
/* 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. */
#include <setjmp.h>
struct ThreadContext {
jmp_buf _jmp_context;
};
#define save_thread_context(switched, context) \
{ \
(*(switched)) = setjmp((context)->_jmp_context); \
}
#define switch_to_thread_context(context) \
longjmp((context)->_jmp_context, 1)
#endif /* HAVE_UCONTEXT_H */
#ifdef __cplusplus
extern "C" {
#endif
typedef void ContextFunction(void *);
void init_thread_context(struct ThreadContext *context,
unsigned char *stack, size_t stack_size,
ContextFunction *main_func, void *data);
/* These two functions are defined as macros, above. */
/* void save_thread_context(int *switched, struct ThreadContext *context); */
/* void switch_to_thread_context(struct ThreadContext *context); */
#ifdef __cplusplus
}
#endif
#endif /* THREAD_SIMPLE_IMPL */
#endif /* CONTEXTSWITCH_H */

View File

@ -126,7 +126,7 @@ start(ThreadPriority priority, bool joinable) {
PyThreadState_Swap(_python_state);
#endif // HAVE_PYTHON
setup_context();
init_thread_context(&_context, _stack, _stack_size, st_begin_thread, this);
_manager->enqueue_ready(this);
return true;
@ -194,81 +194,25 @@ yield_this() {
}
////////////////////////////////////////////////////////////////////
// Function: ThreadSimpleImpl::setup_context
// Access: Private
// Description: Fills the _jmp_context with an appropriate context buffer
// and an appropriate stack reserved for the thread.
// Function: ThreadSimpleImpl::st_begin_thread
// Access: Private, Static
// Description: This method is called as the first introduction to a
// new thread.
////////////////////////////////////////////////////////////////////
void ThreadSimpleImpl::
setup_context() {
#ifdef HAVE_UCONTEXT_H
// Set up a unique thread context using makecontext().
getcontext(&_ucontext);
_ucontext.uc_stack.ss_sp = _stack;
_ucontext.uc_stack.ss_size = _stack_size;
_ucontext.uc_stack.ss_flags = 0;
_ucontext.uc_link = NULL;
makecontext(&_ucontext, (void (*)())setup_context_2, 1, this);
#else // HAVE_UCONTEXT_H
// The setjmp hack for setting up a unique thread context is a bit
// more complicated. 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.
// This requires jumping through a couple of different functions.
// One of these functions, setup_context_2(), is defined in a
// different file, so we can easily disable compiler optimizations
// on that one function.
jmp_buf orig_stack;
if (setjmp(orig_stack) == 0) {
// First, switch to the new stack. This requires temporarily saving
// the this pointer as a static.
_st_this = this;
// Save the current context using setjmp(). This saves out all of
// the processor register values, though it doesn't muck with the
// stack.
jmp_buf temp;
if (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.
((void *&)temp[JB_SP]) = (_st_this->_stack + _st_this->_stack_size);
// And finally, we place ourselves on the new stack by using
// longjmp() to reload the saved (and modified) context.
longjmp(temp, 1);
}
// This is the second return from setjmp. Now we're on the new
// stack.
setup_context_2(_st_this);
// Now restore the original stack and return.
longjmp(orig_stack, 1);
}
// By now we are back to the original stack.
#endif // HAVE_UCONTEXT_H
st_begin_thread(void *data) {
ThreadSimpleImpl *self = (ThreadSimpleImpl *)data;
self->begin_thread();
}
////////////////////////////////////////////////////////////////////
// Function: ThreadSimpleImpl::setup_context_3
// Access: Private, Static
// Description: More continuation of setup_context(). Again, making
// this a separate function helps defeat the compiler
// optimizer.
// Function: ThreadSimpleImpl::begin_thread
// Access: Private
// Description: This method is called as the first introduction to a
// new thread.
////////////////////////////////////////////////////////////////////
void ThreadSimpleImpl::
setup_context_3() {
begin_thread() {
#ifdef HAVE_PYTHON
PyThreadState_Swap(_python_state);
#endif // HAVE_PYTHON
@ -276,7 +220,7 @@ setup_context_3() {
// Here we are executing within the thread. Run the thread_main
// function defined for this thread.
_parent_obj->thread_main();
// Now we have completed the thread.
_status = S_finished;
@ -291,6 +235,7 @@ setup_context_3() {
_manager->next_context();
// Shouldn't get here.
nassertv(false);
abort();
}

View File

@ -27,6 +27,7 @@
#include "pnotify.h"
#include "threadPriority.h"
#include "pvector.h"
#include "contextSwitch.h"
#ifdef HAVE_PYTHON
@ -35,39 +36,6 @@
#endif // HAVE_PYTHON
#ifdef HAVE_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.
#include <ucontext.h>
#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.
#include <setjmp.h>
// Ideally, 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 JB_SP
#if defined(IS_OSX) && defined(__i386__)
// We have determined this value empirically, via test_setjmp.cxx in
// this directory.
#define JB_SP 9
#elif defined(WIN32)
// We have determined this value empirically, via test_setjmp.cxx in
// this directory.
#define JB_SP 4
#endif
#endif // JB_SP
#endif // HAVE_UCONTEXT_H
class Thread;
class ThreadSimpleManager;
class MutexSimpleImpl;
@ -120,9 +88,8 @@ public:
INLINE double get_start_time() const;
private:
void setup_context();
static void *setup_context_2(ThreadSimpleImpl *self);
void setup_context_3();
static void st_begin_thread(void *data);
void begin_thread();
private:
enum Status {
@ -145,12 +112,7 @@ private:
// should wake up.
double _start_time;
#ifdef HAVE_UCONTEXT_H
ucontext_t _ucontext;
#else
jmp_buf _jmp_context;
#endif // HAVE_UCONTEXT_H
ThreadContext _context;
unsigned char *_stack;
size_t _stack_size;

View File

@ -1,59 +0,0 @@
// Filename: threadSimpleImpl_setup_context.cxx
// Created by: drose (20Jun07)
//
////////////////////////////////////////////////////////////////////
//
// PANDA 3D SOFTWARE
// Copyright (c) 2001 - 2004, Disney Enterprises, Inc. All rights reserved
//
// All use of this software is subject to the terms of the Panda 3d
// Software license. You should have received a copy of this license
// along with this source code; you will also find a current copy of
// the license at http://etc.cmu.edu/panda3d/docs/license/ .
//
// To contact the maintainers of this program write to
// panda3d-general@lists.sourceforge.net .
//
////////////////////////////////////////////////////////////////////
#include "threadSimpleImpl.h"
#ifdef THREAD_SIMPLE_IMPL
////////////////////////////////////////////////////////////////////
// Function: ThreadSimpleImpl::setup_context_2
// Access: Private, Static
// Description: Continuation of setup_context(). We have this as a
// separate method so we can ensure that the stack gets
// set up with our this pointer properly. It is defined
// in a separate file so we can disable compiler
// optimizations on this one method (gcc prefers to
// disable optimizations globally on the command line,
// not via pragmas).
////////////////////////////////////////////////////////////////////
void *ThreadSimpleImpl::
setup_context_2(ThreadSimpleImpl *self) {
ThreadSimpleImpl *volatile v_self = self;
#ifndef HAVE_UCONTEXT_H
if (setjmp(self->_jmp_context) == 0) {
// The _jmp_context is now set up and ready to run. Now we can
// return.
return v_self;
}
#endif // HAVE_UCONTEXT_H
// Here we are executing within the thread.
v_self->setup_context_3();
// We shouldn't get here.
abort();
// Even though this line should never be executed, setting it here
// seems to help the compiler figure out not to optimize away
// v_self.
*v_self = 0;
return v_self;
}
#endif // THREAD_SIMPLE_IMPL

View File

@ -66,6 +66,12 @@ enqueue_ready(ThreadSimpleImpl *thread) {
////////////////////////////////////////////////////////////////////
void ThreadSimpleManager::
enqueue_sleep(ThreadSimpleImpl *thread, double seconds) {
if (thread_cat.is_debug()) {
thread_cat.debug()
<< *_current_thread->_parent_obj << " sleeping for "
<< seconds << " seconds\n";
}
double now = get_current_time();
thread->_start_time = now + seconds;
_sleeping.push_back(thread);
@ -186,38 +192,18 @@ next_context() {
_current_thread->_python_state = PyThreadState_Swap(NULL);
#endif // HAVE_PYTHON
#ifdef HAVE_UCONTEXT_H
// The setcontext() implementation.
volatile bool context_return = false;
getcontext(&_current_thread->_ucontext);
if (context_return) {
// We have returned from a setcontext, and are now resuming the
// current thread.
int switched;
save_thread_context(&switched, &_current_thread->_context);
if (switched) {
// Pass 2: we have returned into the context, and are now resuming
// the current thread.
#ifdef HAVE_PYTHON
PyThreadState_Swap(_current_thread->_python_state);
#endif // HAVE_PYTHON
return;
}
// Set this flag so that we can differentiate between getcontext()
// returning the first time and the second time.
context_return = true;
#else
// The longjmp() implementation.
if (setjmp(_current_thread->_jmp_context) != 0) {
// We have returned from a longjmp, and are now resuming the
// current thread.
#ifdef HAVE_PYTHON
PyThreadState_Swap(_current_thread->_python_state);
#endif // HAVE_PYTHON
return;
}
#endif // HAVE_UCONTEXT_H
// Pass 1: we have saved the context successfully.
while (!_finished.empty() && _finished.front() != _current_thread) {
ThreadSimpleImpl *finished_thread = _finished.front();
@ -264,9 +250,9 @@ next_context() {
double wait = _sleeping.front()->_start_time - now;
if (wait > 0.0) {
if (thread_cat.is_spam()) {
thread_cat.spam()
<< "Sleeping " << wait << " seconds\n";
if (thread_cat.is_debug()) {
thread_cat.debug()
<< "Sleeping all threads " << wait << " seconds\n";
}
system_sleep(wait);
}
@ -293,14 +279,11 @@ next_context() {
<< " blocked, " << _sleeping.size() << " sleeping)\n";
}
#ifdef HAVE_UCONTEXT_H
setcontext(&_current_thread->_ucontext);
#else
longjmp(_current_thread->_jmp_context, 1);
#endif
switch_to_thread_context(&_current_thread->_context);
// Shouldn't get here.
nassertv(false);
abort();
}
////////////////////////////////////////////////////////////////////