/*****************************************************************************
 * $Source: /expand6/cvsroot/charm/src/QuickThreads/stp.c,v $
 * $Author: milind $
 * $Date: 2000/03/02 11:37:10 $
 * $Revision: 1.2 $
 *****************************************************************************/

#include "copyright.h"
#include "qt.h"
#include "stp.h"

#ifndef NULL
#define NULL	0
#endif

#define STP_STKSIZE (0x1000)

/* `alignment' must be a power of 2. */
#define STP_STKALIGN(sp, alignment) \
  ((void *)((((qt_word_t)(sp)) + (alignment) - 1) & ~((alignment)-1)))


/* The notion of a thread is merged with the notion of a queue.
   Thread stuff: thread status (sp) and stuff to use during
   (re)initialization.  Queue stuff: next thread in the queue
   (next). */

struct stp_t {
  qt_t *sp;              /* QuickThreads handle. */
  void *sto;             /* `malloc'-allocated stack. */
  struct stp_t *next;    /* Next thread in the queue. */
};


/* A queue is a circular list of threads.  The queue head is a
   designated list element.  If this is a uniprocessor-only
   implementation we can store the `main' thread in this, but in a
   multiprocessor there are several `heavy' threads but only one run
   queue.  A fancier implementation might have private run queues,
   which would lead to a simpler (trivial) implementation */

typedef struct stp_q_t {
  stp_t t;
  stp_t *tail;
} stp_q_t;


/* Helper functions. */

extern void *malloc (unsigned size);
extern void perror (char const *msg);
extern void free (void *sto);

  void *
xmalloc (unsigned size)
{
  void *sto;

  sto = malloc (size);
  if (!sto) {
    perror ("malloc");
    exit (1);
  }
  return (sto);
}

/* Queue access functions. */

  static void
stp_qinit (stp_q_t *q)
{
  q->t.next = q->tail = &q->t;
}


  static stp_t *
stp_qget (stp_q_t *q)
{
  stp_t *t;

  t = q->t.next;
  q->t.next = t->next;
  if (t->next == &q->t) {
    if (t == &q->t) {		/* If it was already empty .. */
      return (NULL);		/* .. say so. */
    }
    q->tail = &q->t;		/* Else now it is empty. */
  }
  return (t);
}


  static void
stp_qput (stp_q_t *q, stp_t *t)
{
  q->tail->next = t;
  t->next = &q->t;
  q->tail = t;
}


/* Thread routines. */

static stp_q_t stp_global_runq;	/* A queue of runable threads. */
static stp_t stp_global_main;   /* Thread for the process. */
static stp_t *stp_global_curr;	/* Currently-executing thread. */

static void *stp_starthelp (qt_t *old, void *ignore0, void *ignore1);
static void stp_only (void *pu, void *pt, qt_userf_t *f);
static void *stp_aborthelp (qt_t *sp, void *old, void *null);
static void *stp_yieldhelp (qt_t *sp, void *old, void *blockq);


  void
stp_init()
{
  stp_qinit (&stp_global_runq);
}


  void
stp_start()
{
  stp_t *next;

  while ((next = stp_qget (&stp_global_runq)) != NULL) {
    stp_global_curr = next;
    QT_BLOCK (stp_starthelp, 0, 0, next->sp);
  }
}


  static void *
stp_starthelp (qt_t *old, void *ignore0, void *ignore1)
{
  stp_global_main.sp = old;
  stp_qput (&stp_global_runq, &stp_global_main);
  /* return (garbage); */
}


  void
stp_create (stp_userf_t *f, void *pu)
{
  stp_t *t;
  void *sto;

  t = xmalloc (sizeof(stp_t));
  t->sto = xmalloc (STP_STKSIZE);
  sto = STP_STKALIGN (t->sto, QT_STKALIGN);
  t->sp = QT_SP (sto, STP_STKSIZE - QT_STKALIGN);
  t->sp = QT_ARGS (t->sp, pu, t, (qt_userf_t *)f, stp_only);
  stp_qput (&stp_global_runq, t);
}


  static void
stp_only (void *pu, void *pt, qt_userf_t *f)
{
  stp_global_curr = (stp_t *)pt;
  (*(stp_userf_t *)f)(pu);
  stp_abort();
  /* NOTREACHED */
}


  void
stp_abort (void)
{
  stp_t *old, *newthread;

  newthread = stp_qget (&stp_global_runq);
  old = stp_global_curr;
  stp_global_curr = newthread;
  QT_ABORT (stp_aborthelp, old, (void *)NULL, newthread->sp);
}


  static void *
stp_aborthelp (qt_t *sp, void *old, void *null)
{
  free (((stp_t *)old)->sto);
  free (old);
  /* return (garbage); */
}


  void
stp_yield()
{
  stp_t *old, *newthread;

  newthread = stp_qget (&stp_global_runq);
  old = stp_global_curr;
  stp_global_curr = newthread;
  QT_BLOCK (stp_yieldhelp, old, &stp_global_runq, newthread->sp);
}


  static void *
stp_yieldhelp (qt_t *sp, void *old, void *blockq)
{
  ((stp_t *)old)->sp = sp;
  stp_qput ((stp_q_t *)blockq, (stp_t *)old);
  /* return (garbage); */
}
