/*=====================================================================*/
/*    serrano/prgm/project/bigloo/fthread/src/Posix/bglthread.c        */
/*    -------------------------------------------------------------    */
/*    Author      :  Manuel Serrano                                    */
/*    Creation    :  Fri Feb 22 12:12:04 2002                          */
/*    Last change :  Fri Nov 14 17:27:00 2003 (serrano)                */
/*    Copyright   :  2002-03 Manuel Serrano                            */
/*    -------------------------------------------------------------    */
/*    C utilities for native Bigloo fair threads implementation.       */
/*=====================================================================*/
#include <pthread.h>
#include <stdlib.h>
#include <gc.h>

#define GC_PRIVATE_H
#include <bglthread.h>


BGL_RUNTIME_DEF obj_t bgl_get_last_error_message( char *  default_message );


/*---------------------------------------------------------------------*/
/*    Global token                                                     */
/*---------------------------------------------------------------------*/
pthread_key_t bglkey;
static pthread_key_t bglidkey;

static bglthread_t token;

static pthread_mutex_t first_lock;
static pthread_cond_t first_cv;
static bgldenv_t bglthread_current_dynamic_env;

static int bglthread_initp = 0;

/*---------------------------------------------------------------------*/
/*    static void                                                      */
/*    init_bglthreads ...                                              */
/*---------------------------------------------------------------------*/
static void
init_bglthreads() {
   if( !bglthread_initp ) {
      bglthread_initp = 1;
      token = 0L;
      
      if( pthread_key_create( &bglkey, 0L ) )
	 goto err;

      if( pthread_key_create( &bglidkey, 0L ) )
	 goto err;

      if( pthread_mutex_init( &first_lock, 0L ) )
	 goto err;
      
      if( pthread_cond_init( &first_cv, 0L ) )
	 goto err;

      return;

err:
      FAILURE( string_to_bstring( "bglthread_init" ),
	       string_to_bstring( "Cannot initialize" ),
	       bgl_get_last_error_message( "unknown error" ) );
   }
}

/*---------------------------------------------------------------------*/
/*    static void *                                                    */
/*    bglthread_run ...                                                */
/*---------------------------------------------------------------------*/
static DWORD WINAPI
bglthread_run( void *arg ) {
   bglthread_t thread = (bglthread_t)arg;

   /* store the thread's stack bottom address */
   /* in the dynamic env per thread structure */
   thread->env->stack_bottom = (char *)&arg;
   
   /* we have to store in the thread, the pointer to the bglthread */
   pthread_setspecific( bglkey, arg );
   
   /* Mark the id for bmem */
   bglthread_id_set( thread, thread->name );
   
/*    printf( "bglthread_run(%s:%d): bglthread=%p thread=%p\n",        */
/* 	   __FILE__, __LINE__, thread, &thread->pthread );             */
   bglthread_wait( thread );

/*    printf( "bglthread_run(%s:%d): bglthread=%p thread=%p je lance la proc...\n", */
/* 	   __FILE__, __LINE__, thread, &thread->pthread );             */
   PROCEDURE_ENTRY( thread->thunk )( thread->thunk, BEOA );
       
   return (DWORD)BUNSPEC;
}

/*---------------------------------------------------------------------*/
/*    bglthread_t                                                      */
/*    bglthread_new ...                                                */
/*---------------------------------------------------------------------*/
bglthread_t
bglthread_new( obj_t proc ) {
   bglthread_t thread = (bglthread_t)GC_MALLOC( sizeof( struct bglthread ) );
   
/*    printf( "bglthread_new(%s:%d): bglthread=%p...\n",               */
/* 	   __FILE__, __LINE__, thread );                               */
   
   init_bglthreads();
   
   thread->thunk = proc;
   thread->name = BUNSPEC;

   thread->env = bgl_dup_dynamic_env( bgl_current_dynamic_env );
   
   if( pthread_mutex_init( &(thread->lock), 0L ) )
      goto err;
   if( pthread_cond_init( &(thread->cv), 0L ) )
      goto err;

/*    printf( "bglthread_new(%s:%d): bglthread=%p...done\n",           */
/* 	   __FILE__, __LINE__, thread );                               */
   return thread;

err:
   FAILURE( string_to_bstring( "make-thread" ),
	    string_to_bstring( "Cannot create thread" ),
	    bgl_get_last_error_message( "unknown error" ) );
}

/*---------------------------------------------------------------------*/
/*    bglthread_t                                                      */
/*    bglthread_name_new ...                                           */
/*---------------------------------------------------------------------*/
bglthread_t
bglthread_new_with_name( obj_t proc, obj_t name ) {
   bglthread_t th = bglthread_new( proc );
   th->name = name;
   return th;
}

/*---------------------------------------------------------------------*/
/*    void                                                             */
/*    bglthread_setup ...                                              */
/*---------------------------------------------------------------------*/
void
bglthread_setup( bglthread_t thread, obj_t obj ) {
   thread->bglobj = obj;
}

/*---------------------------------------------------------------------*/
/*    obj_t                                                            */
/*    bglthread_id_set ...                                             */
/*---------------------------------------------------------------------*/
obj_t
bglthread_id_set( bglthread_t thread, obj_t id ) {
   pthread_setspecific( bglidkey, (void *)id );
}

/*---------------------------------------------------------------------*/
/*    obj_t                                                            */
/*    bglthread_id_get ...                                             */
/*---------------------------------------------------------------------*/
obj_t
bglthread_id_get() {
   return bglthread_initp ? (obj_t)pthread_getspecific( bglidkey ) : 0;
}

/*---------------------------------------------------------------------*/
/*    static bglthread_t                                               */
/*    pthread_current_bglthread ...                                    */
/*---------------------------------------------------------------------*/
static bglthread_t
pthread_current_bglthread() {
   return (bglthread_t)pthread_getspecific( bglkey );
}

/*---------------------------------------------------------------------*/
/*    void                                                             */
/*    bglthread_start ...                                              */
/*---------------------------------------------------------------------*/
void
bglthread_start( bglthread_t thread ) {
   pthread_attr_t a;

   pthread_attr_init( &a );
   pthread_attr_setdetachstate( &a, PTHREAD_CREATE_DETACHED );

   if( pthread_create( &(thread->pthread), &a, bglthread_run, thread ) )
      FAILURE( string_to_bstring( "thread-start!" ),
               string_to_bstring( "Cannot start thread" ),
               bgl_get_last_error_message( "unknown error" ) );
}

/*---------------------------------------------------------------------*/
/*    void                                                             */
/*    bglthread_wait ...                                               */
/*---------------------------------------------------------------------*/
void
bglthread_wait( bglthread_t this ) {
   pthread_mutex_lock( &(this->lock) );
   while( token != this ) {
      pthread_cond_wait( &(this->cv), &(this->lock) );
   }
   pthread_mutex_unlock( &(this->lock) );
}

/*---------------------------------------------------------------------*/
/*    void                                                             */
/*    bglthread_switch ...                                             */
/*---------------------------------------------------------------------*/
void
bglthread_switch( bglthread_t this, bglthread_t next ) {
   pthread_mutex_lock( &(next->lock) );
   
   token = next;
   bgl_current_dynamic_env = next->env;

   pthread_cond_signal( &(next->cv) );
   pthread_mutex_unlock( &(next->lock) );
}

/*---------------------------------------------------------------------*/
/*    void                                                             */
/*    bglthread_enter_scheduler ...                                    */
/*---------------------------------------------------------------------*/
void
bglthread_enter_scheduler( bglthread_t scdl ) {
   pthread_mutex_t *this_lock;
   pthread_cond_t *this_cv;
   bglthread_t this;
   init_bglthreads();

   this = pthread_current_bglthread();

   scdl->parent = this;

   if( this ) {
      this_lock = &(this->lock);
      this_cv = &(this->cv);
   } else {
      this_lock = &first_lock;
      this_cv = &first_cv;
   }

   bglthread_current_dynamic_env = bgl_current_dynamic_env;

   /* switch into the scdl thread */
   bglthread_switch( this, scdl );

   /* wait for the token */
   pthread_mutex_lock( this_lock );
   while( token != this ) pthread_cond_wait( this_cv, this_lock );
   pthread_mutex_unlock( this_lock );
}

/*---------------------------------------------------------------------*/
/*    void                                                             */
/*    bglthread_leave_scheduler ...                                    */
/*---------------------------------------------------------------------*/
void
bglthread_leave_scheduler( bglthread_t scdl ) {
   pthread_mutex_t *parent_lock;
   pthread_cond_t *parent_cv;

   if( scdl->parent ) {
      parent_lock = &(scdl->parent->lock);
      parent_cv = &(scdl->parent->cv);
   } else {
      parent_lock = &first_lock;
      parent_cv = &first_cv;
   }
   
   token = 0L;
   bgl_current_dynamic_env = bglthread_current_dynamic_env;

   pthread_mutex_lock( parent_lock );
   token = scdl->parent;
   pthread_cond_signal( parent_cv );
   pthread_mutex_unlock( parent_lock );

   pthread_mutex_lock( &(scdl->lock) );
   while( token != scdl ) pthread_cond_wait( &(scdl->cv), &(scdl->lock) );
   pthread_mutex_unlock( &(scdl->lock) );
}
   
/*---------------------------------------------------------------------*/
/*    int                                                              */
/*    bglthread_key_create ...                                         */
/*    -------------------------------------------------------------    */
/*    The three following functions (bglthread_key_create,             */
/*    bglthread_setspecific, and bglthread_getspecific) are only       */
/*    used by the Bmem heap profiler. They are not important           */
/*    for the FairThreads themselves.                                  */
/*---------------------------------------------------------------------*/
int
bglthread_key_create( pthread_key_t *key, void *dest ) {
   return pthread_key_create( key, dest );
}

/*---------------------------------------------------------------------*/
/*    int                                                              */
/*    bglthread_setspecific ...                                        */
/*---------------------------------------------------------------------*/
int
bglthread_setspecific( pthread_key_t key, void *val ) {
   return pthread_setspecific( key, val );
}

/*---------------------------------------------------------------------*/
/*    void *                                                           */
/*    bglthread_getspecific ...                                        */
/*---------------------------------------------------------------------*/
void *
bglthread_getspecific( pthread_key_t key ) {
   return pthread_getspecific( key );
}
