Browse Source

move setjmp etc. into contextSwitch.c

David Rose 18 years ago
parent
commit
03ef026f09

+ 2 - 1
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 \

+ 166 - 0
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
+ * [email protected] .
+ *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#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 */

+ 103 - 0
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
+ * [email protected] .
+ *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#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 */
+

+ 15 - 70
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();
 }
 

+ 4 - 42
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 <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;
 

+ 0 - 59
panda/src/pipeline/threadSimpleImpl_no_opt_.cxx

@@ -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
-// [email protected] .
-//
-////////////////////////////////////////////////////////////////////
-
-#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

+ 17 - 34
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();
 }
 
 ////////////////////////////////////////////////////////////////////