/* 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;
const int is_os_threads = 0;

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);
}
