//C-  -*- C++ -*-
//C- -------------------------------------------------------------------
//C- DjVuLibre-3.5
//C- Copyright (c) 2002  Leon Bottou and Yann Le Cun.
//C- Copyright (c) 2001  AT&T
//C-
//C- This software is subject to, and may be distributed under, the
//C- GNU General Public License, Version 2. The license should have
//C- accompanied the software or you may obtain a copy of the license
//C- from the Free Software Foundation at http://www.fsf.org .
//C-
//C- This program is distributed in the hope that it will be useful,
//C- but WITHOUT ANY WARRANTY; without even the implied warranty of
//C- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//C- GNU General Public License for more details.
//C- 
//C- DjVuLibre-3.5 is derived from the DjVu(r) Reference Library
//C- distributed by Lizardtech Software.  On July 19th 2002, Lizardtech 
//C- Software authorized us to replace the original DjVu(r) Reference 
//C- Library notice by the following text (see doc/lizard2002.djvu):
//C-
//C-  ------------------------------------------------------------------
//C- | DjVu (r) Reference Library (v. 3.5)
//C- | Copyright (c) 1999-2001 LizardTech, Inc. All Rights Reserved.
//C- | The DjVu Reference Library is protected by U.S. Pat. No.
//C- | 6,058,214 and patents pending.
//C- |
//C- | This software is subject to, and may be distributed under, the
//C- | GNU General Public License, Version 2. The license should have
//C- | accompanied the software or you may obtain a copy of the license
//C- | from the Free Software Foundation at http://www.fsf.org .
//C- |
//C- | The computer code originally released by LizardTech under this
//C- | license and unmodified by other parties is deemed "the LIZARDTECH
//C- | ORIGINAL CODE."  Subject to any third party intellectual property
//C- | claims, LizardTech grants recipient a worldwide, royalty-free, 
//C- | non-exclusive license to make, use, sell, or otherwise dispose of 
//C- | the LIZARDTECH ORIGINAL CODE or of programs derived from the 
//C- | LIZARDTECH ORIGINAL CODE in compliance with the terms of the GNU 
//C- | General Public License.   This grant only confers the right to 
//C- | infringe patent claims underlying the LIZARDTECH ORIGINAL CODE to 
//C- | the extent such infringement is reasonably necessary to enable 
//C- | recipient to make, have made, practice, sell, or otherwise dispose 
//C- | of the LIZARDTECH ORIGINAL CODE (or portions thereof) and not to 
//C- | any greater extent that may be necessary to utilize further 
//C- | modifications or combinations.
//C- |
//C- | The LIZARDTECH ORIGINAL CODE is provided "AS IS" WITHOUT WARRANTY
//C- | OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
//C- | TO ANY WARRANTY OF NON-INFRINGEMENT, OR ANY IMPLIED WARRANTY OF
//C- | MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
//C- +------------------------------------------------------------------
// 
// $Id: GThreads.cpp,v 1.15 2004/04/21 14:54:43 leonb Exp $
// $Name: release_3_5_14 $

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#if NEED_GNUG_PRAGMAS
# pragma implementation
#endif

// This file defines machine independent classes
// for running and synchronizing threads.
// - Author: Leon Bottou, 01/1998

// From: Leon Bottou, 1/31/2002
// Almost unchanged by Lizardtech.
// GSafeFlags should go because it not as safe as it claims.

#include "GThreads.h"
#include "GException.h"
#include "DjVuMessageLite.h"
#include <stdlib.h>
#include <stdio.h>

// ----------------------------------------
// Consistency check

#if THREADMODEL!=NOTHREADS
#ifdef USE_EXCEPTION_EMULATION
#warning "Compiler must support thread safe exceptions"
#endif //USE_EXCEPTION_EMULATION
#if defined(__GNUC__)
#if (__GNUC__<2) || ((__GNUC__==2) && (__GNUC_MINOR__<=8))
#warning "GCC 2.8 exceptions are not thread safe."
#warning "Use properly configured EGCS-1.1 or greater."
#endif // (__GNUC__<2 ...
#endif // defined(__GNUC__)
#endif // THREADMODEL!=NOTHREADS

#ifndef _DEBUG
#if defined(DEBUG) 
#define _DEBUG /* */
#elif DEBUGLVL >= 1
#define _DEBUG /* */
#endif
#endif

#if THREADMODEL==WINTHREADS
# include <process.h>
#endif
#if THREADMODEL==COTHREADS
# include <setjmp.h>
# include <string.h>
# include <unistd.h>
# include <sys/types.h>
# include <sys/time.h>
#endif


#ifdef HAVE_NAMESPACES
namespace DJVU {
# ifdef NOT_DEFINED // Just to fool emacs c++ mode
}
#endif
#endif


// ----------------------------------------
// NOTHREADS
// ----------------------------------------

#if THREADMODEL==NOTHREADS
int
GThread::create( void (*entry)(void*), void *arg)
{
  (*entry)(arg);
  return 0;
}
#endif


// ----------------------------------------
// WIN32 IMPLEMENTATION
// ----------------------------------------

#if THREADMODEL==WINTHREADS

static unsigned __stdcall 
start(void *arg)
{
  GThread *gt = (GThread*)arg;
  try 
    {
      G_TRY
        {
          gt->xentry( gt->xarg );
        }
      G_CATCH(ex)
        {
          ex.perror();
          DjVuMessageLite::perror( ERR_MSG("GThreads.uncaught") );
#ifdef _DEBUG
          abort();
#endif
        }
      G_ENDCATCH;
    }
  catch(...)
    {
      DjVuMessageLite::perror( ERR_MSG("GThreads.unrecognized") );
#ifdef _DEBUG
      abort();
#endif
    }
  return 0;
}

GThread::GThread(int stacksize)
  : hthr(0), thrid(0), xentry(0), xarg(0)
{
}

GThread::~GThread()
{
  if (hthr)
    CloseHandle(hthr);
  hthr = 0;
  thrid = 0;
}

int  
GThread::create(void (*entry)(void*), void *arg)
{
  if (hthr)
    return -1;
  xentry = entry;
  xarg = arg;
  unsigned uthread = 0;
  hthr = (HANDLE)_beginthreadex(NULL, 0, start, (void*)this, 0, &uthread);
  thrid = (DWORD) uthread;
  if (hthr)
    return 0;
  return -1;
}

void 
GThread::terminate()
{
  OutputDebugString("Terminating thread.\n");
  if (hthr)
    TerminateThread(hthr,0);
}

int
GThread::yield()
{
  Sleep(0);
  return 0;
}

void *
GThread::current()
{
  return (void*) GetCurrentThreadId();
}

struct thr_waiting {
  struct thr_waiting *next;
  struct thr_waiting *prev;
  BOOL   waiting;
  HANDLE gwait;
};

GMonitor::GMonitor()
  : ok(0), count(1), head(0), tail(0)
{
  InitializeCriticalSection(&cs);
  locker = GetCurrentThreadId();
  ok = 1;
}

GMonitor::~GMonitor()
{
  ok = 0;
  EnterCriticalSection(&cs);
  for (struct thr_waiting *w=head; w; w=w->next)
    SetEvent(w->gwait);
  LeaveCriticalSection(&cs);
  DeleteCriticalSection(&cs); 
}

void 
GMonitor::enter()
{
  DWORD self = GetCurrentThreadId();
  if (count>0 || self!=locker)
    {
      if (ok)
        EnterCriticalSection(&cs);
      locker = self;
      count = 1;
    }
  count -= 1;
}

void 
GMonitor::leave()
{
  DWORD self = GetCurrentThreadId();
  if (ok && (count>0 || self!=locker))
    G_THROW( ERR_MSG("GThreads.not_acq_broad") );
  count += 1;
  if (count > 0)
    {
      count = 1;
      if (ok)
        LeaveCriticalSection(&cs);
    }
}

void
GMonitor::signal()
{
  if (ok)
    {
      DWORD self = GetCurrentThreadId();
      if (count>0 || self!=locker)
        G_THROW( ERR_MSG("GThreads.not_acq_signal") );
      for (struct thr_waiting *w=head; w; w=w->next)
        if (w->waiting) 
          {
            SetEvent(w->gwait);
            w->waiting = FALSE;
            break; // Only one thread is allowed to run!
          }
    }
}

void
GMonitor::broadcast()
{
  if (ok)
    {
      DWORD self = GetCurrentThreadId();
      if (count>0 || self!=locker)
        G_THROW( ERR_MSG("GThreads.not_acq_broad") );
      for (struct thr_waiting *w=head; w; w=w->next)
        if (w->waiting)
            {
              SetEvent(w->gwait);
              w->waiting = FALSE;
            }
    }
}

void
GMonitor::wait()
{
  // Check state
  DWORD self = GetCurrentThreadId();
  if (count>0 || self!=locker)
    G_THROW( ERR_MSG("GThreads.not_acq_wait") );
  // Wait
  if (ok)
    {
      // Prepare wait record
      struct thr_waiting waitrec;
      waitrec.waiting = TRUE;
      waitrec.gwait = CreateEvent(NULL,FALSE,FALSE,NULL);
      waitrec.next = 0;
      waitrec.prev = tail;
      // Link wait record (protected by critical section)
      *(waitrec.next ? &waitrec.next->prev : &tail) = &waitrec; 
      *(waitrec.prev ? &waitrec.prev->next : &head) = &waitrec;
      // Start wait
      int sav_count = count;
      count = 1;
      LeaveCriticalSection(&cs);
      WaitForSingleObject(waitrec.gwait,INFINITE);
      // Re-acquire
      EnterCriticalSection(&cs);
      count = sav_count;
      locker = self;
      // Unlink wait record
      *(waitrec.next ? &waitrec.next->prev : &tail) = waitrec.prev;
      *(waitrec.prev ? &waitrec.prev->next : &head) = waitrec.next;
      CloseHandle(waitrec.gwait);
    }
}

void
GMonitor::wait(unsigned long timeout) 
{
  // Check state
  DWORD self = GetCurrentThreadId();
  if (count>0 || self!=locker)
    G_THROW( ERR_MSG("GThreads.not_acq_wait") );
  // Wait
  if (ok)
    {
      // Prepare wait record
      struct thr_waiting waitrec;
      waitrec.waiting = TRUE;
      waitrec.gwait = CreateEvent(NULL,FALSE,FALSE,NULL);
      waitrec.next = 0;
      waitrec.prev = tail;
      // Link wait record (protected by critical section)
      *(waitrec.prev ? &waitrec.prev->next : &head) = &waitrec;
      *(waitrec.next ? &waitrec.next->prev : &tail) = &waitrec; 
      // Start wait
      int sav_count = count;
      count = 1;
      LeaveCriticalSection(&cs);
      WaitForSingleObject(waitrec.gwait,timeout);
      // Re-acquire
      EnterCriticalSection(&cs);
      count = sav_count;
      locker = self;
      // Unlink wait record
      *(waitrec.next ? &waitrec.next->prev : &tail) = waitrec.prev;
      *(waitrec.prev ? &waitrec.prev->next : &head) = waitrec.next;
      CloseHandle(waitrec.gwait);
    }
}

#endif



// ----------------------------------------
// MACTHREADS IMPLEMENTATION (from Praveen)
// ----------------------------------------

#if THREADMODEL==MACTHREADS

// Doubly linked list of waiting threads
struct thr_waiting {
  struct thr_waiting *next;     // ptr to next waiting thread record
  struct thr_waiting *prev;     // ptr to ptr to this waiting thread
  unsigned long thid;           // id of waiting thread
  int *wchan;                   // cause of the wait
};
static struct thr_waiting *first_waiting_thr = 0;
static struct thr_waiting *last_waiting_thr = 0;


// Stops current thread. 
// Argument ``self'' must be current thread id.
// Assumes ``ThreadBeginCritical'' has been called before.
static void
macthread_wait(ThreadID self, int *wchan)
{
  // Prepare and link wait record
  struct thr_waiting wait; // no need to malloc :-)
  wait.thid = self;
  wait.wchan = wchan;
  wait.next = 0;
  wait.prev = last_waiting_thr;
  *(wait.prev ? &wait.prev->next : &first_waiting_thr ) = &wait;
  *(wait.next ? &wait.next->prev : &last_waiting_thr ) = &wait;
  // Leave critical section and start waiting.
  (*wchan)++;
  SetThreadStateEndCritical(self, kStoppedThreadState, kNoThreadID);
  // The Apple documentation says that the above call reschedules a new
  // thread.  Therefore it will only return when the thread wakes up.
  ThreadBeginCritical();
  (*wchan)--;
  // Unlink wait record
  *(wait.prev ? &wait.prev->next : &first_waiting_thr ) = wait.next;
  *(wait.next ? &wait.next->prev : &last_waiting_thr ) = wait.prev;
  // Returns from the wait.
}

// Wakeup one thread or all threads waiting on cause wchan
static void
macthread_wakeup(int *wchan, int onlyone)
{
  if (*wchan == 0)
    return;
  for (struct thr_waiting *q=first_waiting_thr; q; q=q->next)
    if (q->wchan == wchan) {
      // Found a waiting thread
      q->wchan = 0;
      SetThreadState(q->thid, kReadyThreadState, kNoThreadID);
      if (onlyone)
        return;
    }
}

GThread::GThread(int stacksize) 
  : thid(kNoThreadID), xentry(0), xarg(0)
{
}

GThread::~GThread(void)
{
  thid = kNoThreadID;
}

pascal void *
GThread::start(void *arg)
{
  GThread *gt = (GThread*)arg;
  try 
    {
      G_TRY
        {
          (gt->xentry)(gt->xarg);
        }
      G_CATCH(ex)
        {
          ex.perror();
          DjVuMessageLite::perror( ERR_MSG("GThreads.uncaught") );
#ifdef _DEBUG
          abort();
#endif
        }
      G_ENDCATCH;
    }
  catch(...)
    {
      DjVuMessageLite::perror( ERR_MSG("GThreads.unrecognized") );
#ifdef _DEBUG
      abort();
#endif
    }
  return 0;
}

int
GThread::create(void (*entry)(void*), void *arg)
{
  if (xentry || thid!=kNoThreadID)
    return -1;
  xentry = entry;
  xarg = arg;
  int err = NewThread( kCooperativeThread, GThread::start , this, 0,
                       kCreateIfNeeded, (void**)nil, &thid );
  if( err != noErr )
    return err;
  return 0;
}

void
GThread::terminate()
{
  if (thid != kNoThreadID) {
    DisposeThread( thid, NULL, false );
    thid = kNoThreadID;
  }
}

int
GThread::yield()
{
  YieldToAnyThread();
  return 0;
}

void*
GThread::current()
{
  unsigned long thid = kNoThreadID;
  GetCurrentThread(&thid);
  return (void*) thid;
}


// GMonitor implementation
GMonitor::GMonitor() 
  : ok(0), count(1), locker(0), wlock(0), wsig(0)
{
  locker = kNoThreadID;
  ok = 1;
}

GMonitor::~GMonitor() 
{
  ok = 0;
  ThreadBeginCritical();
  macthread_wakeup(&wsig, 0);
  macthread_wakeup(&wlock, 0);
  ThreadEndCritical();
  YieldToAnyThread();
}

void 
GMonitor::enter() 
{
  ThreadID self;
  GetCurrentThread(&self);
  ThreadBeginCritical();
  if (count>0 || self!=locker)
    {
      while (ok && count<=0)
        macthread_wait(self, &wlock);
      count = 1;
      locker = self;
    }
  count -= 1;
  ThreadEndCritical();
}

void 
GMonitor::leave() 
{
  ThreadID self;
  GetCurrentThread(&self);
  if (ok && (count>0 || self!=locker))
    G_THROW( ERR_MSG("GThreads.not_acq_leave") );
  ThreadBeginCritical();
  if (++count > 0)
    macthread_wakeup(&wlock, 1);
  ThreadEndCritical();
}

void 
GMonitor::signal() 
{
  ThreadID self;
  GetCurrentThread(&self);
  if (count>0 || self!=locker)
    G_THROW( ERR_MSG("GThreads.not_acq_signal") );
  ThreadBeginCritical();
  macthread_wakeup(&wsig, 1);
  ThreadEndCritical();
}

void 
GMonitor::broadcast() 
{
  ThreadID self;
  GetCurrentThread(&self);
  if (count>0 || self!=locker)
    G_THROW( ERR_MSG("GThreads.not_acq_broad") );
  ThreadBeginCritical();
  macthread_wakeup(&wsig, 0);
  ThreadEndCritical();
}

void 
GMonitor::wait() 
{
  // Check state
  ThreadID self;
  GetCurrentThread(&self);
  if (count>0 || locker!=self)
    G_THROW( ERR_MSG("GThreads.not_acq_wait") );
  // Wait
  if (ok)
    {
      // Atomically release monitor and wait
      ThreadBeginCritical();
      int sav_count = count;
      count = 1;
      macthread_wakeup(&wlock, 1);
      macthread_wait(self, &wsig);
      // Re-acquire
      while (ok && count<=0)
        macthread_wait(self, &wlock);
      count = sav_count;
      locker = self;
      ThreadEndCritical();
    }
}

void 
GMonitor::wait(unsigned long timeout) 
{
  // Timeouts are not used for anything important.
  // Just ignore the timeout and wait the regular way.
  wait();
}

#endif



// ----------------------------------------
// POSIXTHREADS IMPLEMENTATION
// ----------------------------------------

#if THREADMODEL==POSIXTHREADS

#if defined(CMA_INCLUDE)
#define DCETHREADS
#define pthread_key_create pthread_keycreate
#else
#define pthread_mutexattr_default  NULL
#define pthread_condattr_default   NULL
#endif


void *
GThread::start(void *arg)
{
  GThread *gt = (GThread*)arg;
#ifdef DCETHREADS
#ifdef CANCEL_ON
  pthread_setcancel(CANCEL_ON);
  pthread_setasynccancel(CANCEL_ON);
#endif
#else // !DCETHREADS
#ifdef PTHREAD_CANCEL_ENABLE
  pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, 0);
#endif
#ifdef PTHREAD_CANCEL_ASYNCHRONOUS
  pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, 0);
#endif
#endif
  // Catch exceptions
#ifdef __EXCEPTIONS
  try 
    {
#endif 
      G_TRY
        {
          (gt->xentry)(gt->xarg);
        }
      G_CATCH(ex)
        {
          ex.perror();
          DjVuMessageLite::perror( ERR_MSG("GThreads.uncaught") );
#ifdef _DEBUG
          abort();
#endif
        }
      G_ENDCATCH;
#ifdef __EXCEPTIONS
    }
  catch(...)
    {
          DjVuMessageLite::perror( ERR_MSG("GThreads.unrecognized") );
#ifdef _DEBUG
      abort();
#endif
    }
#endif
  return 0;
}


// GThread

GThread::GThread(int stacksize) : 
  hthr(0), xentry(0), xarg(0)
{
}

GThread::~GThread()
{
  hthr = 0;
}

int  
GThread::create(void (*entry)(void*), void *arg)
{
  if (xentry || xarg)
    return -1;
  xentry = entry;
  xarg = arg;
#ifdef DCETHREADS
  int ret = pthread_create(&hthr, pthread_attr_default, GThread::start, (void*)this);
  if (ret >= 0)
    pthread_detach(hthr);
#else
  pthread_attr_t attr;
  pthread_attr_init(&attr);
  pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
  int ret = pthread_create(&hthr, &attr, start, (void*)this);
  pthread_attr_destroy(&attr);
#endif
  return ret;
}

void 
GThread::terminate()
{
  if (xentry || xarg)
    pthread_cancel(hthr);
}

int
GThread::yield()
{
#ifdef DCETHREADS
  pthread_yield();
#else
  // should use sched_yield() when available.
  static struct timeval timeout = { 0, 0 };
  ::select(0, 0,0,0, &timeout);
#endif
  return 0;
}

void*
GThread::current()
{
  pthread_t self = pthread_self();
#if defined(pthread_getunique_np)
  return (void*) pthread_getunique_np( & self );
#elif defined(cma_thread_get_unique)
  return (void*) cma_thread_get_unique( & self );  
#else
  return (void*) self;
#endif
}

// -- GMonitor

GMonitor::GMonitor()
  : ok(0), count(1), locker(0)
{
  // none of this should be necessary ... in theory.
#ifdef PTHREAD_MUTEX_INITIALIZER
  static pthread_mutex_t tmutex=PTHREAD_MUTEX_INITIALIZER;
  memcpy(&mutex,&tmutex,sizeof(mutex));
#endif
#ifdef PTHREAD_COND_INITIALIZER
  static pthread_cond_t tcond=PTHREAD_COND_INITIALIZER;
  memcpy(&cond,&tcond,sizeof(cond));
#endif
  // standard
  pthread_mutex_init(&mutex, pthread_mutexattr_default);
  pthread_cond_init(&cond, pthread_condattr_default); 
  locker = pthread_self();
  ok = 1;
}

GMonitor::~GMonitor()
{
  ok = 0;
  pthread_cond_destroy(&cond);
  pthread_mutex_destroy(&mutex); 
}


void 
GMonitor::enter()
{
  pthread_t self = pthread_self();
  if (count>0 || !pthread_equal(locker, self))
    {
      if (ok)
        pthread_mutex_lock(&mutex);
      locker = self;
      count = 1;
    }
  count -= 1;
}

void 
GMonitor::leave()
{
  pthread_t self = pthread_self();
  if (ok && (count>0 || !pthread_equal(locker, self)))
    G_THROW( ERR_MSG("GThreads.not_acq_broad") );
  count += 1;
  if (count > 0)
    {
      count = 1;
      if (ok)
        pthread_mutex_unlock(&mutex);
    }
}

void
GMonitor::signal()
{
  if (ok)
    {
      pthread_t self = pthread_self();
      if (count>0 || !pthread_equal(locker, self))
        G_THROW( ERR_MSG("GThreads.not_acq_signal") );
      pthread_cond_signal(&cond);
    }
}

void
GMonitor::broadcast()
{
  if (ok)
    {
      pthread_t self = pthread_self();
      if (count>0 || !pthread_equal(locker, self))
        G_THROW( ERR_MSG("GThreads.not_acq_broad") );
      pthread_cond_broadcast(&cond);
    }
}

void
GMonitor::wait()
{
  // Check
  pthread_t self = pthread_self();
  if (count>0 || !pthread_equal(locker, self))
    G_THROW( ERR_MSG("GThreads.not_acq_wait") );
  // Wait
  if (ok)
    {
      // Release
      int sav_count = count;
      count = 1;
      // Wait
      pthread_cond_wait(&cond, &mutex);
      // Re-acquire
      count = sav_count;
      locker = self;
    }      
}

void
GMonitor::wait(unsigned long timeout) 
{
  // Check
  pthread_t self = pthread_self();
  if (count>0 || !pthread_equal(locker, self))
    G_THROW( ERR_MSG("GThreads.not_acq_wait") );
  // Wait
  if (ok)
    {
      // Release
      int sav_count = count;
      count = 1;
      // Wait
      struct timeval  abstv;
      struct timespec absts;
      gettimeofday(&abstv, NULL); // grrr
      absts.tv_sec = abstv.tv_sec + timeout/1000;
      absts.tv_nsec = abstv.tv_usec*1000  + (timeout%1000)*1000000;
      if (absts.tv_nsec > 1000000000) {
        absts.tv_nsec -= 1000000000;
        absts.tv_sec += 1;
      }
      pthread_cond_timedwait(&cond, &mutex, &absts);
      // Re-acquire
      count = sav_count;
      locker = self;
    }      
}

#endif



// ----------------------------------------
// CUSTOM COOPERATIVE THREADS
// ----------------------------------------

#if THREADMODEL==COTHREADS

#ifndef __GNUG__
#error "COTHREADS require G++"
#endif
#if (__GNUC__<2) || ((__GNUC__==2) && (__GNUC_MINOR__<=90))
#warning "COTHREADS require EGCS-1.1.1 with Leon's libgcc patch."
#warning "You may have trouble with thread-unsafe exceptions..."
#define NO_LIBGCC_HOOKS
#endif

// -------------------------------------- constants

// Minimal stack size
#define MINSTACK   (32*1024)
// Default stack size
#define DEFSTACK   (127*1024)
// Maxtime between checking fdesc (ms)
#define MAXFDWAIT    (200)
// Maximum time to wait in any case
#define MAXWAIT (60*60*1000)
// Maximum penalty for hog task (ms)
#define MAXPENALTY (1000)
// Trace task switches
#undef COTHREAD_TRACE
#undef COTHREAD_TRACE_VERBOSE

// -------------------------------------- context switch code

struct mach_state { 
  jmp_buf buf; 
};

static void
mach_switch(mach_state *st1, mach_state *st2)
{ 
#if #cpu(sparc)
  asm("ta 3"); // save register windows
#endif
  if (! setjmp(st1->buf))
    longjmp(st2->buf, 1);
}

static void 
mach_start(mach_state *st1, void *pc, char *stacklo, char *stackhi)
{ 
#if #cpu(sparc)
  asm("ta 3"); // save register windows
#endif
  if (! setjmp(st1->buf))
    {
      // The following code must perform two tasks:
      // -- set stack pointer to a proper value between #stacklo# and #stackhi#.
      // -- branch to or call the function at address #pc#.
      // This function never returns ... so there is no need to save anything
#if #cpu(mips)
      char *sp = (char*)(((unsigned long)stackhi-16) & ~0xff);
      asm volatile ("move $sp,%0\n\t"  // set new stack pointer
                    "move $25,%1\n\t"  // call subroutine via $25
                    "jal  $25\n\t"     // call subroutine via $25
                    "nop"              // delay slot
                    : : "r" (sp), "r" (pc) );
#elif #cpu(i386)
      char *sp = (char*)(((unsigned long)stackhi-16) & ~0xff);
      asm volatile ("movl %0,%%esp\n\t" // set new stack pointer
                    "call *%1"          // call function
                    : : "r" (sp), "r" (pc) );
#elif #cpu(sparc)
      char *sp = (char*)(((unsigned long)stackhi-16) & ~0xff);
      asm volatile ("ta 3\n\t"          // saving the register windows will not hurt.
                    "mov %0,%%sp\n\t"   // set new stack pointer
                    "call %1,0\n\t"     // call function
                    "nop"               // delay slot
                    : : "r" (sp), "r" (pc) );
#elif #cpu(hppa)
      char *sp = (char*)(((unsigned long)stacklo+128+255) & ~0xff);
      asm volatile("copy %0,%%sp\n\t"       // set stack pointer
                   "copy %1,%%r22\n\t"      // set call address
                   ".CALL\n\t"              // call pseudo instr (why?)
                   "bl $$dyncall,%%r31\n\t" // call 
                   "copy %%r31,%%r2"        // delay slot ???
                   : : "r" (sp), "r" (pc) );
#elif #cpu(alpha)
      char *sp = (char*)(((unsigned long)stackhi-16) & ~0xff);
      asm volatile ("bis $31,%0,$30\n\t"  // set new stack pointer
                    "bis $31,%1,$27\n\t"  // load function pointer
                    "jsr $26,($27),0"     // call function
                    : : "r" (sp), "r" (pc) );
#elif #cpu(powerpc)
      char *sp = (char*)(((unsigned long)stackhi-16) & ~0xff);
      asm volatile ("mr 1,%0\n\t"         // set new stack pointer
                    "mr 0,%1\n\t"         // load func pointer into r0
                    "mtlr 0\n\t"          // load link register with r0
                    "blrl"                // branch
                    : : "r" (sp), "r" (pc) );
#elif #cpu(m68k) && defined(COTHREAD_UNTESTED)
      char *sp = (char*)(((unsigned long)stackhi-16) & ~0xff);
      asm volatile ("move%.l %0,%Rsp\n\t" // set new stack pointer
                    "jmp %a1"             // branch to address %1
                    : : "r" (sp), "a" (pc) );
#elif #cpu(arm) && defined(COTHREAD_UNTESTED)
      char *sp = (char*)(((unsigned long)stackhi-16) & ~0xff);
      asm volatile ("mov%?\t%|sp, %0\n\t" // set new stack pointer
                    "mov%?\t%|pc, %1"     // branch to address %1
                    : : "r" (sp), "r" (pc) );
#else
#error "COTHREADS not supported on this machine."
#error "Try -DTHREADMODEL=NOTHREADS."
#endif
      // We should never reach this point
      abort();
      // Note that this call to abort() makes sure
      // that function mach_start() is compiled as a non-leaf
      // function. It is indeed a non-leaf function since the
      // piece of assembly code calls a function, but the compiler
      // would not know without the call to abort() ...
    }
}

#ifdef CHECK
// This code can be used to verify that task switching works.
char stack[16384];
mach_state st1, st2;
void th2() {
  puts("2b"); mach_switch(&st2, &st1);
  puts("4b"); mach_switch(&st2, &st1);
  puts("6b"); mach_switch(&st2, &st1);
}
void th2relay() {
  th2(); puts("ooops\n");
}
void th1() {
  mach_start(&st1, (void*)th2relay, stack, stack+sizeof(stack));
  puts("3a"); mach_switch(&st1, &st2);
  puts("5a"); mach_switch(&st1, &st2);
}
int main() { 
  puts("1a"); th1(); puts("6a"); 
}
#endif



// -------------------------------------- select

struct coselect {
  int nfds;
  fd_set rset;
  fd_set wset;
  fd_set eset;
};

static void 
coselect_merge(coselect *dest, coselect *from)
{
  int i;
  int nfds = from->nfds;
  if (nfds > dest->nfds)
    dest->nfds = nfds;
  for (i=0; i<nfds; i++) if (FD_ISSET(i, &from->rset)) FD_SET(i, &dest->rset);
  for (i=0; i<nfds; i++) if (FD_ISSET(i, &from->wset)) FD_SET(i, &dest->wset);
  for (i=0; i<nfds; i++) if (FD_ISSET(i, &from->eset)) FD_SET(i, &dest->eset);
}

static int
coselect_test(coselect *c)
{
  static timeval tmzero = {0,0};
  fd_set copyr = c->rset;
  fd_set copyw = c->wset;
  fd_set copye = c->eset;
  return select(c->nfds, &copyr, &copyw, &copye, &tmzero);
}


// -------------------------------------- cotask

class GThread::cotask {
public:
#ifndef NO_LIBGCC_HOOKS
  cotask(const int xstacksize,void *);
#else
  cotask(const int xstacksize);
#endif
  ~cotask();
  class GThread::cotask *next;
  class GThread::cotask *prev;
  // context
  mach_state regs;
  // stack information
  char *stack;
  GPBuffer<char> gstack;
  int stacksize;
  // timing information
  unsigned long over;
  // waiting information
  void *wchan;
  coselect *wselect;
  unsigned long *maxwait;
  // delete after termination
  bool autodelete;
  // egcs exception support
#ifndef NO_LIBGCC_HOOKS
  void *ehctx;
#endif
};

#ifndef NO_LIBGCC_HOOKS
GThread::cotask::cotask(const int xstacksize, void *xehctx)
#else
GThread::cotask::cotask(const int xstacksize)
#endif
: next(0), prev(0), gstack(stack,xstacksize), stacksize(xstacksize),
  over(0), wchan(0), wselect(0), maxwait(0), autodelete(false)
#ifndef NO_LIBGCC_HOOKS
  ,ehctx(xehctx)
#endif
{
  memset(&regs,0,sizeof(regs));
}

static GThread::cotask *maintask = 0;
static GThread::cotask *curtask  = 0;
static GThread::cotask *autodeletetask = 0;
static unsigned long globalmaxwait = 0;
static void (*scheduling_callback)(int) = 0;
static timeval time_base;


GThread::cotask::~cotask()
{
  gstack.resize(0);
#ifndef NO_LIBGCC_HOOKS
  if (ehctx)
    free(ehctx);
  ehctx = 0;
#endif
}

static void 
cotask_free(GThread::cotask *task)
{
#ifdef COTHREAD_TRACE
  DjVuPrintErrorUTF8("cothreads: freeing task %p with autodelete=%d\n", 
          task,task->autodelete);
#endif
  if (task!=maintask)
  {
    delete task;
  }
}


// -------------------------------------- time

static unsigned long
time_elapsed(int reset=1)
{
  timeval tm;
  gettimeofday(&tm, NULL);
  long msec = (tm.tv_usec-time_base.tv_usec)/1000;
  unsigned long elapsed = (long)(tm.tv_sec-time_base.tv_sec)*1000 + msec;
  if (reset && elapsed>0)
    {
#ifdef COTHREAD_TRACE
#ifdef COTHREAD_TRACE_VERBOSE
      DjVuPrintErrorUTF8("cothreads: %4ld ms in task %p\n", elapsed, curtask);
#endif
#endif
      time_base.tv_sec = tm.tv_sec;
      time_base.tv_usec += msec*1000;
    }
  return elapsed;
}


// -------------------------------------- scheduler

static int
cotask_yield()
{
  // ok
  if (! maintask)
    return 0;
  // get elapsed time and return immediately when it is too small
  unsigned long elapsed = time_elapsed();
  if (elapsed==0 && curtask->wchan==0 && curtask->prev && curtask->next)
    return 0;
  // adjust task running time
  curtask->over += elapsed;
  if (curtask->over > MAXPENALTY)
    curtask->over = MAXPENALTY;
  // start scheduling
 reschedule:
  // try unblocking tasks
  GThread::cotask *n = curtask->next;
  GThread::cotask *q = n;
  do 
    { 
      if (q->wchan)
        {
	  if (q->maxwait && *q->maxwait<=elapsed) 
            {
              *q->maxwait = 0;
              q->wchan=0; 
              q->maxwait=0; 
              q->wselect=0; 
            }
          else if (q->wselect && globalmaxwait<=elapsed && coselect_test(q->wselect))
            {
              q->wchan=0;
              if (q->maxwait)
                *q->maxwait -= elapsed;
              q->maxwait = 0; 
              q->wselect=0; 
            }
          if (q->maxwait)
            *q->maxwait -= elapsed;
        }
      q = q->next;
    } 
  while (q!=n);
  // adjust globalmaxwait
  if (globalmaxwait < elapsed)
    globalmaxwait = MAXFDWAIT;
  else
    globalmaxwait -= elapsed;
  // find best candidate
  static int count;
  unsigned long best = MAXPENALTY + 1;
  GThread::cotask *r = 0;
  count = 0;
  q = n;
  do 
    { 
      if (! q->wchan)
        {
          count += 1;
          if (best > q->over)
            {
              r = q;
              best = r->over;
            } 
        }
      q = q->next;
    } 
  while (q != n);
  // found
  if (count > 0)
    {
      // adjust over 
      q = n;
      do 
        { 
          q->over = (q->over>best ? q->over-best : 0);
          q = q->next;
        } 
      while (q != n);
      // Switch
      if (r != curtask)
        {
#ifdef COTHREAD_TRACE
          DjVuPrintErrorUTF8("cothreads: ----- switch to %p [%ld]\n", r, best);
#endif
          GThread::cotask *old = curtask;
          curtask = r;
          mach_switch(&old->regs, &curtask->regs);
        }
      // handle autodelete
      if (autodeletetask && autodeletetask->autodelete) 
        cotask_free(autodeletetask);
      autodeletetask = 0;
      // return 
      if (count == 1)
        return 1;
      return 0;
    }
  // No task ready
  count = 0;
  unsigned long minwait = MAXWAIT;
  coselect allfds;
  allfds.nfds = 1;
  FD_ZERO(&allfds.rset);
  FD_ZERO(&allfds.wset);
  FD_ZERO(&allfds.eset);
  q = n;
  do 
    {
      if (q->maxwait || q->wselect)
        count += 1;
      if (q->maxwait && *q->maxwait<minwait)
        minwait = *q->maxwait;
      if (q->wselect)
        coselect_merge(&allfds, q->wselect);
      q = q->next;
    } 
  while (q != n);
  // abort on deadlock
  if (count == 0) {
    DjVuMessageLite::perror( ERR_MSG("GThreads.panic") );
    abort();
  }
  // select
  timeval tm;
  tm.tv_sec = minwait/1000;
  tm.tv_usec = 1000*(minwait-1000*tm.tv_sec);
  select(allfds.nfds,&allfds.rset, &allfds.wset, &allfds.eset, &tm);
  // reschedule
  globalmaxwait = 0;
  elapsed = time_elapsed();
  goto reschedule;
}


static void
cotask_terminate(GThread::cotask *task)
{
#ifdef COTHREAD_TRACE
  DjVuPrintErrorUTF8("cothreads: terminating task %p\n", task);
#endif
  if (task && task!=maintask)
    {
      if (task->prev && task->next)
        {
          if (scheduling_callback)
            (*scheduling_callback)(GThread::CallbackTerminate);
          task->prev->next = task->next;
          task->next->prev = task->prev;
          // mark task as terminated
          task->prev = 0; 
          // self termination
          if (task == curtask)
            {
              if (task->autodelete)
                autodeletetask = task;
              cotask_yield();
            }
        }
    }
}


static void
cotask_wakeup(void *wchan, int onlyone)
{
  if (maintask && curtask)
    {
      GThread::cotask *n = curtask->next;
      GThread::cotask *q = n;
      do 
        { 
          if (q->wchan == wchan)
            {
              q->wchan=0; 
              q->maxwait=0; 
              q->wselect=0; 
              q->over = 0;
	      if (onlyone)
		return;
            }
          q = q->next;
        } 
      while (q!=n);
    }
}


// -------------------------------------- select / get_select

static int
cotask_select(int nfds, 
              fd_set *rfds, fd_set *wfds, fd_set *efds,
              struct timeval *tm)
{
  // bypass
  if (maintask==0 || (tm && tm->tv_sec==0 && tm->tv_usec<1000))
    return select(nfds, rfds, wfds, efds, tm);
  // copy parameters
  unsigned long maxwait = 0;
  coselect parm;
  // set waiting info
  curtask->wchan = (void*)&parm;
  if (rfds || wfds || efds)
    {
      parm.nfds = nfds;
      if (rfds) { parm.rset=*rfds; } else { FD_ZERO(&parm.rset); }
      if (wfds) { parm.wset=*wfds; } else { FD_ZERO(&parm.wset); }
      if (efds) { parm.eset=*efds; } else { FD_ZERO(&parm.eset); }
      curtask->wselect = &parm;
    }
  if (tm) 
    {
      maxwait = time_elapsed(0) + tm->tv_sec*1000 + tm->tv_usec/1000;
      curtask->maxwait = &maxwait;
    }
  // reschedule
  cotask_yield();
  // call select to update masks
  if (tm)
    {
      tm->tv_sec = maxwait/1000;
      tm->tv_usec = 1000*(maxwait-1000*tm->tv_sec);
    }
  static timeval tmzero = {0,0};
  return select(nfds, rfds, wfds, efds, &tmzero);
}


static void 
cotask_get_select(int &nfds, fd_set *rfds, fd_set *wfds, fd_set *efds, 
                  unsigned long &timeout)
{
  int ready = 1;
  unsigned long minwait = MAXWAIT;
  unsigned long elapsed = time_elapsed(0);
  coselect allfds;
  allfds.nfds=0;
  FD_ZERO(&allfds.rset);
  FD_ZERO(&allfds.wset);
  FD_ZERO(&allfds.eset);
  if (curtask)
    {
      GThread::cotask *q=curtask->next;
      while (q != curtask)
        {
          ready++;
          if (q->wchan)
            {
              if (q->wselect) 
                coselect_merge(&allfds, q->wselect);
              if (q->maxwait && *q->maxwait<minwait)
                minwait = *q->maxwait;
              ready--;
            }
          q = q->next;
        }
    }
  timeout = 0;
  nfds=allfds.nfds;
  *rfds=allfds.rset;
  *wfds=allfds.wset;
  *efds=allfds.eset;
  if (ready==1 && minwait>elapsed)
    timeout = minwait-elapsed;
}



// -------------------------------------- libgcc hook

#ifndef NO_LIBGCC_HOOKS
// These are exported by Leon's patched version of libgcc.a
// Let's hope that the egcs people will include the patch in
// the distributions.
extern "C" 
{
  extern void* (*__get_eh_context_ptr)(void);
  extern void* __new_eh_context(void);
}

// This function is called via the pointer __get_eh_context_ptr
// by the internal mechanisms of egcs.  It must return the 
// per-thread event handler context.  This is necessary to
// implement thread safe exceptions on some machine and/or 
// when flag -fsjlj-exception is set.
static void *
cotask_get_eh_context()
{
  if (curtask)
    return curtask->ehctx;
  else if (maintask)
    return maintask->ehctx;
  DjVuMessageLite::perror( ERR_MSG("GThreads.co_panic") );
  abort();
}
#endif



// -------------------------------------- GThread

void 
GThread::set_scheduling_callback(void (*call)(int))
{
  if (scheduling_callback)
    G_THROW( ERR_MSG("GThreads.dupl_callback") );
  scheduling_callback = call;
}


GThread::GThread(int stacksize)
  : task(0), xentry(0), xarg(0)
{
  // check argument
  if (stacksize < 0)
    stacksize = DEFSTACK;
  if (stacksize < MINSTACK)
    stacksize = MINSTACK;
  // initialization
  if (! maintask)
    {
#ifndef NO_LIBGCC_HOOKS
      static GThread::cotask comaintask(0,(*__get_eh_context_ptr)());
      __get_eh_context_ptr = cotask_get_eh_context;
#else
      static GThread::cotask comaintask(0);
#endif
      maintask = &comaintask;
//      memset(maintask, 0, sizeof(GThread::cotask));
      maintask->next = maintask;
      maintask->prev = maintask;
      gettimeofday(&time_base,NULL);
      curtask = maintask;
    }
  // allocation
#ifndef NO_LIBGCC_HOOKS
  task = new GThread::cotask(stacksize,__new_eh_context());
#else
  task = new GThread::cotask(stacksize);
#endif
}


GThread::~GThread()
{
  if (task && task!=maintask)
  {
    if (task->prev) // running
      task->autodelete = true;
    else
      cotask_free(task);
    task = 0;
  }
}

#if __GNUC__ >= 3
# if __GNUC_MINOR__ >= 4
#  define noinline __attribute__((noinline,used))
# elif __GNUC_MINOR >= 2
#  define noinline __attribute__((noinline))
# endif
#endif
#ifndef noinline
# define noinline /**/
#endif

static noinline void startone(void);
static noinline void starttwo(GThread *thr);
static GThread * volatile starter;

static void
startone(void)
{
  GThread *thr = starter;
  mach_switch(&thr->task->regs, &curtask->regs);
  // Registers may still contain an improper pointer
  // to the exception context.  We should neither 
  // register cleanups nor register handlers.
  starttwo(thr);
  abort();
}

static void 
starttwo(GThread *thr)
{
  // Hopefully this function reacquires 
  // an exception context pointer. Therefore
  // we can register the exception handlers.
  // It is placed after ``startone'' to avoid inlining.
#ifdef __EXCEPTIONS
  try 
    {
#endif 
      G_TRY
        {
          thr->xentry( thr->xarg );
        }
      G_CATCH(ex)
        {
          ex.perror();
          DjVuMessageLite::perror( ERR_MSG("GThreads.uncaught") );
#ifdef _DEBUG
          abort();
#endif
        }
      G_ENDCATCH;
#ifdef __EXCEPTIONS
    }
  catch(...)
    {
          DjVuMessageLite::perror( ERR_MSG("GThreads.unrecognized") );
#ifdef _DEBUG
      abort();
#endif
    }
#endif 
  cotask_terminate(curtask);
  GThread::yield();
  // Do not add anything below this line!
  // Nothing should reach it anyway.
  abort();
}

int 
GThread::create(void (*entry)(void*), void *arg)
{
  if (task->next || task->prev)
    return -1;
  xentry = entry;
  xarg = arg;
  task->wchan = 0;
  task->next = curtask;
  task->prev = curtask->prev;
  task->next->prev = task;
  task->prev->next = task;
  GThread::cotask *old = curtask;
  starter = this;
  mach_start(&old->regs, (void*)startone, 
             task->stack, task->stack+task->stacksize);
  if (scheduling_callback)
    (*scheduling_callback)(CallbackCreate);
  return 0;
}


void 
GThread::terminate()
{
  if (task && task!=maintask)
    cotask_terminate(task);
}

int
GThread::yield()
{
  return cotask_yield();
}

int 
GThread::select(int nfds, 
                fd_set *readfds, fd_set *writefds, fd_set *exceptfds,
                struct timeval *timeout)
{
  return cotask_select(nfds, readfds, writefds, exceptfds, timeout);
}

void
GThread::get_select(int &nfds, fd_set *rfds, fd_set *wfds, fd_set *efds, 
                    unsigned long &timeout)
{
  cotask_get_select(nfds, rfds, wfds, efds, timeout);
}

inline void *
GThread::current()
{
  if (curtask && curtask!=maintask)
    return (void*)curtask;
  return (void*)0;
}


// -------------------------------------- GMonitor

GMonitor::GMonitor()
  : count(1), locker(0), wlock(0), wsig(0)
{
  locker = 0;
  ok = 1;
}

GMonitor::~GMonitor()
{
  ok = 0;
  cotask_wakeup((void*)&wsig, 0);
  cotask_wakeup((void*)&wlock, 0);    
  cotask_yield();
  // Because we know how the scheduler works, we know that this single call to
  // yield will run all unblocked tasks and given them the chance to leave the
  // scope of the monitor object.
}

void 
GMonitor::enter()
{
  void *self = GThread::current();
  if (count>0 || self!=locker)
    {
      while (ok && count<=0)
        {
          curtask->wchan = (void*)&wlock;
          wlock++;
          cotask_yield();
          wlock--;
        }
      count = 1;
      locker = self;
    }
  count -= 1;
}

void 
GMonitor::leave()
{
  void *self = GThread::current();
  if (ok && (count>0 || self!=locker))
    G_THROW( ERR_MSG("GThreads.not_acq_leave") );
  if (++count > 0 && wlock > 0)
    cotask_wakeup((void*)&wlock, 1);
}

void
GMonitor::signal()
{
  void *self = GThread::current();
  if (count>0 || self!=locker)
    G_THROW( ERR_MSG("GThreads.not_acq_signal") );
  if (wsig > 0)
    {
      cotask_wakeup((void*)&wsig, 1);
      if (scheduling_callback)
        (*scheduling_callback)(GThread::CallbackUnblock);
    }
}

void
GMonitor::broadcast()
{
  void *self = GThread::current();
  if (count>0 || self!=locker)
    G_THROW( ERR_MSG("GThreads.not_acq_broad") );
  if (wsig > 0)
    {
      cotask_wakeup((void*)&wsig, 0);
      if (scheduling_callback)
        (*scheduling_callback)(GThread::CallbackUnblock);
    }
}

void
GMonitor::wait()
{
  // Check state
  void *self = GThread::current();
  if (count>0 || locker!=self)
    G_THROW( ERR_MSG("GThreads.not_acq_wait") );
  // Wait
  if (ok)
    {
      // Atomically release monitor and wait
      int sav_count = count;
      count = 1;
      curtask->wchan = (void*)&wsig;
      cotask_wakeup((void*)&wlock, 1);
      wsig++;
      cotask_yield();
      wsig--;
      // Re-acquire
      while (ok && count <= 0)
        {
          curtask->wchan = (void*)&wlock;
          wlock++;
          cotask_yield();
          wlock--;
        }
      count = sav_count;
      locker = self;
    }
}

void
GMonitor::wait(unsigned long timeout) 
{
  // Check state
  void *self = GThread::current();
  if (count>0 || locker!=self)
    G_THROW( ERR_MSG("GThreads.not_acq_wait") );
  // Wait
  if (ok)
    {
      // Atomically release monitor and wait
      int sav_count = count;
      count = 1;
      unsigned long maxwait = time_elapsed(0) + timeout;
      curtask->maxwait = &maxwait;
      curtask->wchan = (void*)&wsig;
      cotask_wakeup((void*)&wlock, 1);
      wsig++;
      cotask_yield();
      wsig--;
      // Re-acquire
      while (ok && count<=0)
        {
          curtask->wchan = (void*)&wlock;
          wlock++;
          cotask_yield();
          wlock--;
        }
      count = sav_count;
      locker = self;
    }
}

#endif




// ----------------------------------------
// GSAFEFLAGS 
// ----------------------------------------



GSafeFlags &
GSafeFlags::operator=(long xflags)
{
   enter();
   if (flags!=xflags)
   {
      flags=xflags;
      broadcast();
   }
   leave();
   return *this;
}

GSafeFlags::operator long(void) const
{
   long f;
   ((GSafeFlags *) this)->enter();
   f=flags;
   ((GSafeFlags *) this)->leave();
   return f;
}

bool
GSafeFlags::test_and_modify(long set_mask, long clr_mask,
			    long set_mask1, long clr_mask1)
{
   enter();
   if ((flags & set_mask)==set_mask &&
       (~flags & clr_mask)==clr_mask)
   {
      long new_flags=flags;
      new_flags|=set_mask1;
      new_flags&=~clr_mask1;
      if (new_flags!=flags)
      {
	 flags=new_flags;
	 broadcast();
      }
      leave();
      return true;
   }
   leave();
   return false;
}

void
GSafeFlags::wait_and_modify(long set_mask, long clr_mask,
			    long set_mask1, long clr_mask1)
{
   enter();
   while((flags & set_mask)!=set_mask ||
	 (~flags & clr_mask)!=clr_mask) wait();
   long new_flags=flags;
   new_flags|=set_mask1;
   new_flags&=~clr_mask1;
   if (flags!=new_flags)
   {
      flags=new_flags;
      broadcast();
   }
   leave();
}



#ifdef HAVE_NAMESPACES
}
# ifndef NOT_USING_DJVU_NAMESPACE
using namespace DJVU;
# endif
#endif
