From 03ef026f094cda91fe6b3215608801f7afeb44fe Mon Sep 17 00:00:00 2001 From: David Rose Date: Fri, 22 Jun 2007 00:59:51 +0000 Subject: [PATCH] move setjmp etc. into contextSwitch.c --- panda/src/pipeline/Sources.pp | 3 +- panda/src/pipeline/contextSwitch.c | 166 ++++++++++++++++++ panda/src/pipeline/contextSwitch.h | 103 +++++++++++ panda/src/pipeline/threadSimpleImpl.cxx | 85 ++------- panda/src/pipeline/threadSimpleImpl.h | 46 +---- .../src/pipeline/threadSimpleImpl_no_opt_.cxx | 59 ------- panda/src/pipeline/threadSimpleManager.cxx | 51 ++---- 7 files changed, 307 insertions(+), 206 deletions(-) create mode 100644 panda/src/pipeline/contextSwitch.c create mode 100644 panda/src/pipeline/contextSwitch.h delete mode 100644 panda/src/pipeline/threadSimpleImpl_no_opt_.cxx diff --git a/panda/src/pipeline/Sources.pp b/panda/src/pipeline/Sources.pp index f5245785f2..ecb7c286ab 100644 --- a/panda/src/pipeline/Sources.pp +++ b/panda/src/pipeline/Sources.pp @@ -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 \ diff --git a/panda/src/pipeline/contextSwitch.c b/panda/src/pipeline/contextSwitch.c new file mode 100644 index 0000000000..64300415b6 --- /dev/null +++ b/panda/src/pipeline/contextSwitch.c @@ -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 +#include + +#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 */ diff --git a/panda/src/pipeline/contextSwitch.h b/panda/src/pipeline/contextSwitch.h new file mode 100644 index 0000000000..a179d95187 --- /dev/null +++ b/panda/src/pipeline/contextSwitch.h @@ -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 + +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 + +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 */ + diff --git a/panda/src/pipeline/threadSimpleImpl.cxx b/panda/src/pipeline/threadSimpleImpl.cxx index a252b91ad3..14165d6b15 100644 --- a/panda/src/pipeline/threadSimpleImpl.cxx +++ b/panda/src/pipeline/threadSimpleImpl.cxx @@ -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(); } diff --git a/panda/src/pipeline/threadSimpleImpl.h b/panda/src/pipeline/threadSimpleImpl.h index 74d61ad846..9f8b035336 100644 --- a/panda/src/pipeline/threadSimpleImpl.h +++ b/panda/src/pipeline/threadSimpleImpl.h @@ -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 - -#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 - -// 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; diff --git a/panda/src/pipeline/threadSimpleImpl_no_opt_.cxx b/panda/src/pipeline/threadSimpleImpl_no_opt_.cxx deleted file mode 100644 index 1be1a6859a..0000000000 --- a/panda/src/pipeline/threadSimpleImpl_no_opt_.cxx +++ /dev/null @@ -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 diff --git a/panda/src/pipeline/threadSimpleManager.cxx b/panda/src/pipeline/threadSimpleManager.cxx index e893a1e10a..f43a424a69 100644 --- a/panda/src/pipeline/threadSimpleManager.cxx +++ b/panda/src/pipeline/threadSimpleManager.cxx @@ -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(); } ////////////////////////////////////////////////////////////////////