David Rose 18 years ago
parent
commit
16562fc4e5

+ 7 - 0
panda/src/pipeline/test_setjmp.cxx

@@ -22,6 +22,12 @@
 
 int
 main(int argc, char *argv[]) {
+
+  // If we have ucontext.h, we don't need to use setjmp, so don't
+  // bother trying to compile this program (it may not compile
+  // anyway).
+
+#ifndef HAVE_UCONTEXT_H
   jmp_buf buf1, buf2;
   char * volatile scratch;
 
@@ -39,6 +45,7 @@ main(int argc, char *argv[]) {
   }
   cerr << "scratch = " << (void *)scratch << "\n";
   cerr << "scratch end = " << (void *)(scratch + 1024) << "\n";
+#endif  // HAVE_UCONTEXT_H
     
   return 0;
 }

+ 26 - 2
panda/src/pipeline/threadSimpleImpl.cxx

@@ -196,11 +196,34 @@ yield_this() {
 ////////////////////////////////////////////////////////////////////
 //     Function: ThreadSimpleImpl::setup_context
 //       Access: Private
-//  Description: Fills the _context with an appropriate context buffer
+//  Description: Fills the _jmp_context with an appropriate context buffer
 //               and an appropriate stack reserved for the 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
@@ -234,11 +257,12 @@ setup_context() {
   }
 
   // By now we are back to the original stack.
+#endif  // HAVE_UCONTEXT_H
 }
 
 ////////////////////////////////////////////////////////////////////
 //     Function: ThreadSimpleImpl::setup_context_3
-//       Access: Private
+//       Access: Private, Static
 //  Description: More continuation of setup_context().  Again, making
 //               this a separate function helps defeat the compiler
 //               optimizer.

+ 27 - 3
panda/src/pipeline/threadSimpleImpl.h

@@ -35,9 +35,24 @@
 
 #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>
 
-#if !defined(JB_SP) && defined(IS_OSX) && defined(__i386__)
+// 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
@@ -49,6 +64,10 @@
 
 #endif
 
+#endif  // JB_SP
+
+#endif  // HAVE_UCONTEXT_H
+
 class Thread;
 class ThreadSimpleManager;
 class MutexSimpleImpl;
@@ -102,7 +121,7 @@ public:
 
 private:
   void setup_context();
-  static void setup_context_2(ThreadSimpleImpl *self);
+  static void *setup_context_2(ThreadSimpleImpl *self);
   void setup_context_3();
 
 private:
@@ -126,7 +145,12 @@ private:
   // should wake up.
   double _start_time;
 
-  jmp_buf _context;
+#ifdef HAVE_UCONTEXT_H
+  ucontext_t _ucontext;
+#else
+  jmp_buf _jmp_context;
+#endif  // HAVE_UCONTEXT_H
+
   unsigned char *_stack;
   size_t _stack_size;
 

+ 7 - 8
panda/src/pipeline/threadSimpleImpl_no_opt_.cxx

@@ -31,24 +31,23 @@
 //               disable optimizations globally on the command line,
 //               not via pragmas).
 ////////////////////////////////////////////////////////////////////
-void ThreadSimpleImpl::
+void *ThreadSimpleImpl::
 setup_context_2(ThreadSimpleImpl *self) {
   ThreadSimpleImpl *volatile v_self = self;
-  if (setjmp(self->_context) == 0) {
-    // The _context is now set up and ready to run.  Now we can
+
+#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;
   }
+#endif  // HAVE_UCONTEXT_H
    
   // Here we are executing within the thread.
   v_self->setup_context_3();
 
-  // This code will never run, but we need to have some real code
-  // here, to force the compiler to build an appropriate stack frame
-  // for the above call (otherwise it might optimize the stack frame
-  // away).
-  *v_self = 0;
   abort();
+  return v_self;
 }
 
 #endif  // THREAD_SIMPLE_IMPL

+ 33 - 5
panda/src/pipeline/threadSimpleManager.cxx

@@ -38,9 +38,9 @@ ThreadSimpleManager *ThreadSimpleManager::_global_ptr;
 ////////////////////////////////////////////////////////////////////
 ThreadSimpleManager::
 ThreadSimpleManager() {
+  _current_thread = NULL;
   _clock = TrueClock::get_global_ptr();
   _waiting_for_exit = NULL;
-  nassertv(_current_thread == NULL);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -49,7 +49,7 @@ ThreadSimpleManager() {
 //  Description: Adds the indicated thread to the ready queue.  The
 //               thread will be executed when its turn comes.  If the
 //               thread is not the currently executing thread, its
-//               _context should be filled appropriately.
+//               _jmp_context should be filled appropriately.
 ////////////////////////////////////////////////////////////////////
 void ThreadSimpleManager::
 enqueue_ready(ThreadSimpleImpl *thread) {
@@ -173,7 +173,7 @@ preempt(ThreadSimpleImpl *thread) {
 //               re-enqueued itself with a call to enqueue(), if it
 //               intends to run again.
 //
-//               This will fill in the current thread's _context
+//               This will fill in the current thread's _jmp_context
 //               member appropriately, and then change the global
 //               current_thread pointer.
 ////////////////////////////////////////////////////////////////////
@@ -187,7 +187,29 @@ next_context() {
   PyThreadState_Swap(_current_thread->_python_state);
 #endif  // HAVE_PYTHON
 
-  if (setjmp(_current_thread->_context) != 0) {
+#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.
+#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
@@ -196,6 +218,7 @@ next_context() {
 
     return;
   }
+#endif  // HAVE_UCONTEXT_H
 
   while (!_finished.empty() && _finished.front() != _current_thread) {
     ThreadSimpleImpl *finished_thread = _finished.front();
@@ -261,7 +284,12 @@ next_context() {
     thread_cat.spam()
       << "Switching to " << *_current_thread->_parent_obj << "\n";
   }
-  longjmp(_current_thread->_context, 1);
+
+#ifdef HAVE_UCONTEXT_H
+  setcontext(&_current_thread->_ucontext);
+#else
+  longjmp(_current_thread->_jmp_context, 1);
+#endif
 
   // Shouldn't get here.
   nassertv(false);