/////////////////////////////////////////////////////////////////////////////
// process.cc
//
// SIMLIB version: 2.18
// Date: 2004-01-25
//
// Copyright (c) 1991-2004 Petr Peringer 
//
// This library is licensed under GNU Library GPL. See the file COPYING.
//

//
//  implementation of interruptable functions (non-preemptive threads?)
//  this module is the only nonportable module in SIMLIB
//  we need to save/restore process stack and working setjmp/longjmp 
//  NOTE: on Sparc it is not enough (due register windows)
//

// TODO: add implementation with stack switching
// TODO: add implementation with preemptive threads

////////////////////////////////////////////////////////////////////////////
// interface
//

#include "simlib.h"
#include "internal.h"

#include <csetjmp>
#include <cstring>

// operating system test
#if !(defined(__MSDOS__)||defined(__linux__)||defined(__WIN32__)||defined(__FreeBSD__)||defined(__i386))
# error "process.cc is not implemented in this system"
#endif /* !__<OS>__ */


////////////////////////////////////////////////////////////////////////////
// implementation
//

SIMLIB_IMPLEMENTATION


////////////////////////////////////////////////////////////////////////////
// machine dependent macros:
// this works on i386+ platforms
/////////////////////////////////////////////////////////////////////////////
#if defined(__BCPLUSPLUS__) || defined(__TURBOC__)  // Borland compilers 
/////////////////////////////////////////////////////////////////////////////

# if defined(__WIN32__)         // 32 bit
#   define _GET_STACK_PTR(v)    { v = (char*)_ESP; }
#   define _SHIFT_STACK_PTR(v)  { (char*)_ESP -= v; }
# else                          // 16 bit (OLD, not supported)
#   if defined(__LARGE__) || defined(__COMPACT__) || defined(__HUGE__)
#     define _GET_STACK_PTR(v)  { v = (char far *)MK_FP(_SS,_SP); }
#   else
#     define _GET_STACK_PTR(v)  { v = (char*)_SP; }
#   endif
#   define _SHIFT_STACK_PTR(v)  { _SP -= v; }
# endif

/////////////////////////////////////////////////////////////////////////////
#elif defined(__GNUC__)         // GNU C++ compiler
/////////////////////////////////////////////////////////////////////////////

# ifdef __sun__ 		// Sparc
#   error "SPARC implementation of Process::Behavior not ready! "
    // this code doesn't work --- the problem: register windows not saved? 
#   define _GET_STACK_PTR(v)    { asm("st %%sp, %0" : "=m" (v)); }
    static volatile int _THREAD_TMP;
#   define _SHIFT_STACK_PTR(v)  { _THREAD_TMP = v;                                 \
                                  asm("sub %%sp, %0, %%sp" : : "r" (_THREAD_TMP)); \
                                }
# elif defined(__i386__)	// i386+ expected...
#   define _GET_STACK_PTR(v)    { asm("movl %%esp,%0":"=r" (v)); }
    static volatile int _THREAD_TMP;
#   define _SHIFT_STACK_PTR(v)  { _THREAD_TMP=v;                             \
                                  asm("movl %0,%%eax": : "m" (_THREAD_TMP)); \
                                  asm("subl %eax,%esp");                     \
                                }
# else 				// other...
#   error "process.cc: this platform is not supported"
# endif

/////////////////////////////////////////////////////////////////////////////
#else  				// other compilers not supported
/////////////////////////////////////////////////////////////////////////////

# error "process.cc: this compiler is not supported"

/////////////////////////////////////////////////////////////////////////////
#endif
/////////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////////
// machine independent code:
////////////////////////////////////////////////////////////////////////////
// memory allocation:
//
# define _THREAD_ALLOC(p,size)  {                                       \
             p = new char[size];                                        \
             if(!p) SIMLIB_error(MemoryError); /* for old compilers */  \
           }
# define _THREAD_FREE(p)  { delete [] p; p = 0; }


////////////////////////////////////////////////////////////////////////////
// stack manipulations:
//
// save the stack state of thread
static volatile char *_THREAD_sp;
#define THREAD_SAVE_STACK()                                             \
  {                                                                     \
    _GET_STACK_PTR(_THREAD_sp);                                         \
    _size = (unsigned)(_StackBase-_THREAD_sp);                          \
    _THREAD_ALLOC(_p, _size);                                           \
    DEBUG(DBG_THREAD,("THREAD_SAVE_STACK(%p,%uB)", _StackBase, _size)); \
    memcpy(_p, (void*)(_StackBase-_size), _size);                       \
  }

// restore the stack state of thread
#define THREAD_RESTORE_STACK()                                          \
  {                                                                     \
    DEBUG(DBG_THREAD,("THREAD_RESTORE_STACK(%p,%uB)",_StackBase,_size));\
    _SHIFT_STACK_PTR(_size);                                            \
    DEBUG(DBG_THREAD,("THREAD_RESTORE_STACK -- after shift"));          \
    memcpy((void*)(_StackBase-_size), _p, _size);                       \
    DEBUG(DBG_THREAD,("THREAD_RESTORE_STACK -- after memcpy"));         \
    _THREAD_FREE(_p);                                                   \
  }

// mark the base of stack
#define THREAD_MARK_STACK()                                             \
  {                                                                     \
    _GET_STACK_PTR(_StackBase);                                         \
    DEBUG(DBG_THREAD,("THREAD_MARK_STACK(%p)", _StackBase));            \
  }


////////////////////////////////////////////////////////////////////////////
// THREADS implementation:

// prepare thread for running
#define THREAD_INIT()                                                   \
{                                                                       \
  _p = 0;                                                               \
  _status = _PREPARED;                                                  \
}

// destroy thread
#define THREAD_DONE()                                                   \
{                                                                       \
  if (_p) { _THREAD_FREE(_p); }                                         \
  _status = _TERMINATED;                                                \
}

// interrupt thread (must be current)
#define THREAD_INTERRUPT()                                              \
{                                                                       \
  /*if(!isCurrent())  SIMLIB_error("Can't interrupt...");*/             \
  _status = _INTERRUPTED;                                               \
  THREAD_SAVE_STACK();                                                  \
  if(!setjmp(_ProcStatus))  longjmp(_StateBuf,1);                       \
}

// end of any thread
#define THREAD_TERMINATE()                                              \
{                                                                       \
  _status = _TERMINATED;                                                \
  if(isCurrent())  longjmp(_StateBuf,2);                                \
  else if(IsAllocated()) delete this; /* remove passive process */         \
}


////////////////////////////////////////////////////////////////////////////
// global variables 
//
static jmp_buf _StateBuf;             // stack state before dispatch
static volatile char *_StackBase=0;   // start of stack data area


////////////////////////////////////////////////////////////////////////////
//  constructor
//
Process::Process(Priority_t p) : Entity(p) {
#ifdef NEW_WULIST
  _wait_until = false;
#endif
  dprintf(("Process::Process(%d)", p));
  THREAD_INIT();          // initialize
}

////////////////////////////////////////////////////////////////////////////
//  destructor
//
Process::~Process() {
  dprintf(("Process::~Process()"));
  THREAD_DONE();          // free memory
#ifdef NEW_WULIST
  if(_wait_until) _WaitUntilRemove();
#endif
}

////////////////////////////////////////////////////////////////////////////
//  Interrupt -- ensures WaitUntil tests
//
void Process::Interrupt()
{
  dprintf(("%s.Interrupt()", Name()));
  SQS::ScheduleFirst(this); // continue after other processes WaitUntil checks
  THREAD_INTERRUPT();
}

////////////////////////////////////////////////////////////////////////////
//  Activate -- activate now
//
void Process::Activate()
{
  Entity::Activate();
  if (!isCurrent()) return;
  THREAD_INTERRUPT();
}

void Process::Activate(double t)       // Activate in time t
{
  Entity::Activate(t);
  if (!isCurrent()) return;
  THREAD_INTERRUPT();
}

////////////////////////////////////////////////////////////////////////////
//  Wait -- wait fo dtime
//
void Process::Wait(double dtime)
{
  Entity::Wait(dtime);
  if (!isCurrent()) return;
  THREAD_INTERRUPT();
}

////////////////////////////////////////////////////////////////////////////
//  Seize -- seize facility
//
void Process::Seize(Facility &f, ServicePriority_t sp /* = 0 */)
{
  (&f)->Seize(this,sp);  // use polymorphic interface
}

////////////////////////////////////////////////////////////////////////////
//  Release -- release facility
//
void Process::Release(Facility &f)
{
  (&f)->Release(this);
}

////////////////////////////////////////////////////////////////////////////
//  Enter --
//
void Process::Enter(Store &s, unsigned long ReqCap)
{
  (&s)->Enter(this,ReqCap); // polymorphic
}

////////////////////////////////////////////////////////////////////////////
//  Leave --
//
void Process::Leave(Store &s, unsigned long ReqCap)
{
  (&s)->Leave(ReqCap);
}

////////////////////////////////////////////////////////////////////////////
// Into --- inserting into queue
//
void Process::Into(Queue &q)
{
  if(where()!=0) Out(); // if already in queue then remove ### +warning ???
  (&q)->Insert(this);   // polymorphic insert
}

////////////////////////////////////////////////////////////////////////////
//  Passivate - deactivation of process
//
void Process::Passivate()              // ***** process interrupt *****
{
  Entity::Passivate();
  if (!isCurrent()) return; // can be passivated by other process
  THREAD_INTERRUPT();
}

////////////////////////////////////////////////////////////////////////////
//  Terminate -- end of process
//
void Process::Terminate()
{
  dprintf(("%s.Terminate()", Name()));
  if(!Idle()) SQS::Get(this); // remove from calendar
  THREAD_TERMINATE(); // ukonceni aktivniho procesu
}

////////////////////////////////////////////////////////////////////////////
//  _Run - process (re)activation
//
void Process::_Run()
{
  dprintf(("%s._Run()", Name()));

  if(_status==_INTERRUPTED || _status==_PREPARED)
  {
    THREAD_MARK_STACK();        // mark the stack base address
    if(!setjmp(_StateBuf))      // save processor status
    {                           // return after process interrupt
      _status = _RUNNING;
      if(_p)
      {                                 // restart - process was run
                                        // ... have saved status
        DEBUG(DBG_THREAD,(" - Thread CONTINUE "));
        THREAD_RESTORE_STACK();         // restore status
        longjmp(_ProcStatus,1);         // continue - reactivation
        // never reach this point       // ??
        SIMLIB_internal_error();
      }
      else
      {
        DEBUG(DBG_THREAD,(" - Thread START "));
        Behavior();                     // start of behavior description
        DEBUG(DBG_THREAD,(" - Thread END "));
        _status = _TERMINATED;
        if (!Idle()) SQS::Get(this);    // remove from calendar
        if (IsAllocated()) delete this;    // destroy
      }
    }
    else // return from Behavior() Interrupt|Terminate
      if (Terminated() && IsAllocated()) delete this;  // destroy
  }
  else
    SIMLIB_error(ProcessNotInitialized);
}


////////////////////////////////////////////////////////////////////////////
// Process::Name --- name of the process
//
const char *Process::Name()
{
  const char *name = SimObject::Name();
  if(*name) return name; // has explicit name
  else      return SIMLIB_create_name("Process#%lu", _Ident);
}

// end

