/*  pl-setup.c,v 1.26 1995/02/07 12:12:31 jan Exp

    Copyright (c) 1990 Jan Wielemaker. All rights reserved.
    See ../LICENCE to find out about your rights.
    jan@swi.psy.uva.nl

    Purpose: Initialise the system
*/

/*#define O_DEBUG 1*/

#define GLOBAL				/* allocate global variables here */
#include "pl-incl.h"
#include <sys/stat.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#undef ulong
#define ulong unsigned long
#undef max
#define max(a,b) ((a) > (b) ? (a) : (b))

#define K * 1024

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
This module initialises the system and defines the global variables.  It
also holds the code  for  dynamically  expanding  stacks  based  on  MMU
access.   Finally  it holds the code to handle signals transparently for
foreign language code or packages with which Prolog was linked together.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

forwards void initStacks(long, long, long, long);
forwards void initFeatures(void);
forwards void initSignals(void);

#undef I
#define I TAGEX_INDIRECT

const unsigned int tagtypeex[] = 
{
	    /* var     int    float   atom   string   list    term     ref */
/* static */	0,	0,	0,	0,	0,	0,	0,	0,
/* heap */	0,	I,	I,	0,	I,	0,	0,	0,
/* global */	0,	I,	I,	0,	I,	0,	0,	0,
/* local */	0,	0,	0,	0,	0,	0,	0,	0
};

#undef I

void
setupProlog(void)
{ DEBUG(1, Sdprintf("Starting Heap Initialisation\n"));

  GD->critical = 0;
  LD->aborted = FALSE;
  signalled = 0;

  startCritical;
  initMemAlloc();
#if HAVE_SIGNAL
  DEBUG(1, Sdprintf("Prolog Signal Handling ...\n"));
  initSignals();
#endif
  DEBUG(1, Sdprintf("Stacks ...\n"));
  initStacks(GD->options.localSize, 
	     GD->options.globalSize, 
	     GD->options.trailSize, 
	     GD->options.argumentSize);

  lTop = lBase;
  tTop = tBase;
  gTop = gBase;
  aTop = aBase;

  base_addresses[STG_LOCAL]  = (unsigned long)lBase;
  base_addresses[STG_GLOBAL] = (unsigned long)gBase;
  base_addresses[STG_TRAIL]  = (unsigned long)tBase;
  DEBUG(1, Sdprintf("base_addresses[STG_LOCAL] = %p\n",
		    base_addresses[STG_LOCAL]));
  DEBUG(1, Sdprintf("base_addresses[STG_GLOBAL] = %p\n",
		    base_addresses[STG_GLOBAL]));
  DEBUG(1, Sdprintf("base_addresses[STG_TRAIL] = %p\n",
		    base_addresses[STG_TRAIL]));

#ifdef O_LIMIT_DEPTH
  depth_limit   = (unsigned long)DEPTH_NO_LIMIT;
  depth_reached = 0;
#endif

  emptyStacks();

  if ( !GD->dumped )
  { DEBUG(1, Sdprintf("Atoms ...\n"));
    initAtoms();
    DEBUG(1, Sdprintf("Features ...\n"));
    initFeatures();
    DEBUG(1, Sdprintf("Functors ...\n"));
    initFunctors();
    DEBUG(1, Sdprintf("Modules ...\n"));
    initTables();
    initModules();
    DEBUG(1, Sdprintf("Records ...\n"));
    initRecords();
    DEBUG(1, Sdprintf("Flags ...\n"));
    initFlags();
    DEBUG(1, Sdprintf("Foreign Predicates ...\n"));
    initBuildIns();
    DEBUG(1, Sdprintf("Operators ...\n"));
    initOperators();
    DEBUG(1, Sdprintf("Arithmetic ...\n"));
    initArith();
    DEBUG(1, Sdprintf("Tracer ...\n"));
    initTracer();
    debugstatus.styleCheck = SINGLETON_CHECK;
    DEBUG(1, Sdprintf("wam_table ...\n"));
    initWamTable();
  } else
  { resetReferences();
    resetGC();				/* reset garbage collector */
    GD->stateList = (State) NULL;	/* all states are already in core */
  }
  DEBUG(1, Sdprintf("IO ...\n"));
  initIO();
  DEBUG(1, Sdprintf("Loader ...\n"));
  resetLoader();
  DEBUG(1, Sdprintf("Symbols ...\n"));
  getSymbols();
  DEBUG(1, Sdprintf("Term ...\n"));
  resetTerm();
  GD->io_initialised = TRUE;

  endCritical;

  environment_frame = (LocalFrame) NULL;
  LD->statistics.inferences = 0;
#if O_STORE_PROGRAM || O_SAVE
  GD->cannot_save_program = NULL;
#else
  GD->cannot_save_program = "Not supported on this machine";
#endif

#if O_XWINDOWS
  DEBUG(1, Sdprintf("XWindows ...\n");
  initXWindows();
#endif

  DEBUG(1, Sdprintf("Heap Initialised\n"));
}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Feature interface
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

void
CSetFeature(char *name, char *value)
{ setFeature(lookupAtom(name), FT_ATOM, lookupAtom(value));
}

static void
CSetIntFeature(char *name, long value)
{ setFeature(lookupAtom(name), FT_INTEGER, value);
}

static void
initFeatures()
{ CSetFeature("arch",		ARCH);
#if __WIN32__
  if ( iswin32s() )
    CSetFeature("win32s",	"true");
  CSetFeature("windows",	"true");
#endif
  CSetIntFeature("version",	PLVERSION);
  if ( systemDefaults.home )
    CSetFeature("home",		systemDefaults.home);
  CSetFeature("c_libs",		C_LIBS);
  CSetFeature("c_staticlibs",	C_STATICLIBS);
  CSetFeature("c_cc",		C_CC);
  CSetFeature("c_ldflags",	C_LDFLAGS);
  CSetFeature("gc",		"true");
  CSetFeature("trace_gc",	"false");
#ifdef O_SAVE
  CSetFeature("save",	       "true");
  CSetFeature("save_program",  "true");
#endif
#ifdef O_STORE_PROGRAM
  CSetFeature("save_program",	"true");
#endif
#if defined(O_FOREIGN) || defined(O_MACH_FOREIGN) || defined(O_AIX_FOREIGN)
  CSetFeature("load_foreign",  "true");
#endif
#if defined(HAVE_DLOPEN) || defined(HAVE_SHL_LOAD)
  CSetFeature("open_shared_object", "true");
#endif
#ifdef O_DLL
  CSetFeature("dll", "true");
#endif
#if O_DYNAMIC_STACKS
  CSetFeature("dynamic_stacks",	"true");
#endif
#ifdef HAVE_POPEN
  CSetFeature("pipe",		"true");
#endif
#ifdef ASSOCIATE_SRC
  CSetFeature("associate",	ASSOCIATE_SRC);
#endif
#ifdef O_DDE
  CSetFeature("dde",		"true");
#endif
#ifdef O_RUNTIME
  CSetFeature("runtime",	"true");
  CSetFeature("debug_on_error",	"false");
  CSetFeature("report_error",	"false");
#else
  CSetFeature("debug_on_error",	"true");
  CSetFeature("report_error",	"true");
#endif
					/* ISO features */
  CSetIntFeature("max_integer", PLMAXINT);
  CSetIntFeature("min_integer", PLMININT);
  CSetIntFeature("max_tagged_integer", PLMAXTAGGEDINT);
  CSetIntFeature("min_tagged_integer", PLMINTAGGEDINT);
  CSetFeature("bounded",	"true");
  if ( (-3 / 2) == -2 )
    CSetFeature("integer_rounding_function", "down");
  else
    CSetFeature("integer_rounding_function", "toward_zero");
  CSetFeature("max_arity", "unbounded");
  CSetFeature("float_format", "%g");
  CSetFeature("character_escapes", "true");
  CSetFeature("tty_control", GD->cmdline.notty ? "false" : "true");
  CSetFeature("allow_variable_name_as_functor", "false");
#if defined(__unix__) || defined(unix)
  CSetFeature("unix", "true");
#endif

#if defined(__DATE__) && defined(__TIME__)
  { char buf[100];

    Ssprintf(buf, "%s, %s", __DATE__, __TIME__);
    CSetFeature("compiled_at",	buf);
  }
#endif
}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
			   SIGNAL HANDLING

SWI-Prolog catches a number of signals.  Interrupt is catched  to  allow
the  user  to interrupt normal execution.  Floating point exceptions are
trapped  to  generate  a  normal   error   or   arithmetic   exceptions.
Segmentation  violations  are  trapped  on  machines  using  the  MMU to
implement stack overflow  checks  and  stack  expansion.   These  signal
handlers  needs  to be preserved over saved states and the system should
allow foreign language code to handle signals without  interfering  with
Prologs signal handlers.  For this reason a layer is wired around the OS
signal handling.

Code in SWI-Prolog should  call  pl_signal()  rather  than  signal()  to
install  signal  handlers.  SWI-Prolog assumes the handler function is a
void function.  On some systems this gives  some  compiler  warnigns  as
they  define  signal handlers to be int functions.  This should be fixed
some day.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

#if HAVE_SIGNAL

#ifdef __WIN32__
#define HAVE_SIGNALS !iswin32s()
#else
#define HAVE_SIGNALS 1
#endif

#if !O_DEBUG
static void
fatal_signal_handler(int sig, int type, SignalContext scp, char *addr)
{ DEBUG(1, Sdprintf("Fatal signal %d\n", sig));

  deliverSignal(sig, type, scp, addr);
}
#endif


#ifdef HAVE_SIGSETMASK
static int defsigmask;
#endif

static void
initSignals(void)
{ int n;

  if ( !GD->dumped )
  { for( n = 0; n < MAXSIGNAL; n++ )
    { LD_sig_handler(n).os = LD_sig_handler(n).user = SIG_DFL;
      LD_sig_handler(n).catched = FALSE;
    }

#ifdef SIGTTOU
    pl_signal(SIGTTOU, SIG_IGN);
#endif
#if !O_DEBUG && !defined(_DEBUG)	/* just crash when debugging */
    pl_signal(SIGSEGV, (handler_t)fatal_signal_handler);
    pl_signal(SIGILL,  (handler_t)fatal_signal_handler);
#ifdef SIGBUS
    pl_signal(SIGBUS,  (handler_t)fatal_signal_handler);
#endif
#endif
  } else
  { for( n = 0; n < MAXSIGNAL; n++ )
      if ( LD_sig_handler(n).os != SIG_DFL )
        signal(n, LD_sig_handler(n).os);
  }

#ifdef HAVE_SIGGETMASK
  defsigmask = siggetmask();
#else
#ifdef HAVE_SIGBLOCK
  defsigmask = sigblock(0);
#endif
#endif
}


void
resetSignals()
{
#ifdef HAVE_SIGSETMASK			/* fixes Linux repeated ^C */
  sigsetmask(defsigmask);
#endif

  signalled = 0L;
}


handler_t
pl_signal(int sig, handler_t func)
{ if ( HAVE_SIGNALS )
  { handler_t old = signal(sig, func);

    DEBUG(1, Sdprintf("pl_signal(%d, %p) --> %p\n", sig, func, old));

#ifdef SIG_ERR
    if ( old == SIG_ERR )
      warning("PL_signal(%d, 0x%x) failed: %s",
	      sig, (unsigned long)func, OsError());
#endif

    LD_sig_handler(sig).os = func;
    LD_sig_handler(sig).catched = (func == SIG_DFL ? FALSE : TRUE);

    return old;
  } else
    return SIG_DFL;
}


void
deliverSignal(int sig, int type, SignalContext scp, char *addr)
{ typedef RETSIGTYPE (*uhandler_t)(int, int, void *, char *);
    
#ifndef BSD_SIGNALS
  signal(sig, LD_sig_handler(sig).os);	/* ??? */
#endif

  if ( LD_sig_handler(sig).user != SIG_DFL )
  { uhandler_t uh = (uhandler_t)LD_sig_handler(sig).user;

    (*uh)(sig, type, scp, addr);
    return;
  }

  sysError("Unexpected signal: %d\n", sig);
}


void
PL_handle_signals()
{ typedef RETSIGTYPE (*uhandler_t)(int);

  while(signalled)
  { ulong mask = 1L;
    int sig = 1;

    for( ; ; mask <<= 1, sig++ )
    { if ( signalled & mask )
      { signalled &= ~mask;

	if ( LD_sig_handler(sig).os == SIG_DFL )
	{ fatalError("Unhandled signal: %d\n", sig);
	} else if ( LD_sig_handler(sig).os != SIG_IGN )
	{ uhandler_t uh = (uhandler_t)LD_sig_handler(sig).os;

	  (*uh)(sig);
	}				/* SIG_IGN: ignored */

	break;
      }
    }
  }
}

#endif /*HAVE_SIGNAL*/

		 /*******************************
		 *	       STACKS		*
		 *******************************/

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Create nice empty stacks. exception_bin   and  exception_printed are two
term-references that must be low on  the   stack  to  ensure they remain
valid while the stack is unrolled after an exception.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

void
emptyStacks()
{ environment_frame = NULL;
  fli_context       = NULL;
  lTop = lBase;
  tTop = tBase;
  gTop = gBase;
  aTop = aBase;

  PL_open_foreign_frame();
  exception_bin     = PL_new_term_ref();
  exception_printed = PL_new_term_ref();
}


#if O_DYNAMIC_STACKS

static void init_stack(Stack s, char *name,
		       caddress base, long limit, long minsize);
static void gcPolicy(Stack s, int policy);

#ifndef NO_SEGV_HANDLING
#ifdef SIGNAL_HANDLER_PROVIDES_ADDRESS
static RETSIGTYPE segv_handler(int sig, int type,
			       SignalContext scp, char *addr);
#else
static RETSIGTYPE segv_handler(int sig);
#endif
#endif

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
STACK_SEPARATION defines the  space  between   the  stacks.  The maximum
discontinuity while writing the local stack  is determined by the number
of variables in the clause.  An example worst case is:

foo :-
	(   failing_goal,
	    bar(term(A, B, C, ....))
	;   hello(AnotherVar)
	).
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

#define STACK_SEPARATION ROUND(MAXVARIABLES*sizeof(word), size_alignment)
#define STACK_MINIMUM    (32 * 1024)

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
			STACK MEMORY MANAGEMENT

In these days some operating systems allows the  user  to  map  physical
memory  anywhere  in  the  virtual  address  space.  For multiple stacks
machines such as Prolog, this is ideal.  The  stacks  can  be  allocated
very  far  appart  with  large  gaps  between  them.   Stack overflow is
detected by hardware and results (in  Unix)  in  a  segmentation  fault.
This fault is trapped and the stack is automatically expanded by mapping
more  memory.

In theory the stacks can be deallocated dynamically as  well,  returning
the  resources to the system.  Currently this can be done explicitely by
calling  trim_stacks/0  and  the  garbage  collector.    It   might   be
interesting  to  do  this  automatically  at  certain points to minimise
memory requirements.  How?

Currently this mechanism can use mmap() and munmap() of SunOs 4.0 or the
system-V shared memory primitives (if they meet certain criteria).
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

#include <errno.h>
#ifndef WIN32
extern int errno;
#endif /*WIN32*/

static int size_alignment;	/* Stack sizes must be aligned to this */
static int base_alignment;	/* Stack bases must be aligned to this */

#undef MB
#define MB * (1024L * 1024L)

static long
align_size(long int x)
{ return x % size_alignment ? (x / size_alignment + 1) * size_alignment : x;
}

static long
align_base(long int x)
{ return x % base_alignment ? (x / base_alignment + 1) * base_alignment : x;
}

static long
align_base_down(long int x)
{ return (x / base_alignment) * base_alignment;
}

#ifdef MMAP_STACK
#include <sys/mman.h>
#include <fcntl.h>

static int mapfd = -1;			/* File descriptor used for mapping */

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Return a file descriptor to a file, open  for  reading  and  holding  at
least  one  page of 0's. On some systems /dev/zero is available for this
trick.  If not, a file of one page is created under the name /tmp/pl-map
if it does not already exists and this file is opened for  reading.   It
can  be  shared  by  many  SWI-Prolog  processes  and (therefore) is not
removed on exit.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

#ifdef HAVE_MAP_ANON
#if !defined(MAP_ANON) && defined(MAP_ANONYMOUS)
#define MAP_ANON MAP_ANONYMOUS
#endif

#define get_map_fd() (-1)
#define STACK_MAP_TYPE MAP_ANON|MAP_PRIVATE|MAP_FIXED

#else /*HAVE_MAP_ANON*/

#define STACK_MAP_TYPE MAP_PRIVATE|MAP_FIXED

static int
get_map_fd()
{ int fd;
  static char *map = "/tmp/pl-map";

  if ( (fd = open("/dev/zero", O_RDONLY)) >= 0 )
    return fd;

  if ( (fd = open(map, O_RDONLY)) < 0 )
  { if ( errno == ENOENT )
    { char buf[1024];
      char *s;
      int n;
      int oldmask = umask(0);

      if ( (fd = open(map, O_RDWR|O_CREAT, 0666)) < 0 )
      { fatalError("Can't create map file %s: %s", map, OsError());
        return -1;
      }
      umask(oldmask);
      for(n=1024, s = buf; n > 0; n--)
        *s++ = EOS;
      for(n=size_alignment/1024; n > 0; n--)
      { if ( write(fd, buf, 1024) != 1024 )
          fatalError("Failed to create map file %s: %s\n", map, OsError());
      }

      return fd;
    }
    fatalError("Can't open map file %s: %s", map, OsError());
    return -1;
  }

  return fd;
}
#endif /*HAVE_MAP_ANON*/

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Estimate the top if the heap. The default is to get the size of the heap
using getrlimit(), add this to the estimated  base and use the result as
top address.

This does not always appewar  to  work.   If  you  know the top, #define
TOPOFHEAP in config.h. Othewise #define  it  to   0,  in  which case the
system will allocate a default heap of 64 MB and the stacks above that.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

#ifdef HAVE_GETRLIMIT
#ifdef HAVE_SYS_RESOURCE_H
#include <sys/resource.h>
#endif

#ifdef RLIMIT_DATA
#ifndef HAVE_RLIM_T
typedef unsigned long rlim_t;
#endif
static ulong
dataLimit()
{ struct rlimit limit;
 
  if ( getrlimit(RLIMIT_DATA, &limit) == 0 )
  { rlim_t datasize = limit.rlim_cur;
    rlim_t maxlong  = (rlim_t)(1L << (LONGBITSIZE-1)) - 1;
 
    if ( datasize > maxlong )
      datasize = (ulong)maxlong;
 
    return datasize;
  }
 
  return 0L;
}
#else
#define dataLimit() (0L)
#endif /*RLIMIT_DATA*/
#else
#define dataLimit() (0L)
#endif /*HAVE_GETRLIMIT*/


#ifdef TOPOFHEAP
#define topOfHeap() TOPOFHEAP
#else /*TOPOFHEAP*/
ulong
topOfHeap()
{ ulong data = dataLimit();

  if ( data )
  { ulong top = heap_base + data;

    DEBUG(1, Sdprintf("Heap: %p ... %p\n", (void *)heap_base, (void *)top));
    return top;
  }
    
  return 0L;
}
#endif /*TOPOFHEAP*/


/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Expand stack `s' by one page.  This might not be  enough,  but  in  this
(very  rare) case another segmentation fault will follow to get the next
page.  The memory is expanded by mapping the map-fd file onto  the  page
using  a  private  map.  This way the contents of the map-file is copied
into the page but all changes to the page are  kept  local.   Note  that
SunOs  4.0.0  on SUN-3 has a bug that causes the various mapped pages to
point to the same physical memory.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

static void
mapOrOutOf(Stack s)
{ ulong incr;

  if ( s->top > s->max )
    incr = ROUND(((ulong)s->top - (ulong)s->max), size_alignment);
  else
    incr = size_alignment;

  if ( (ulong)s->max + incr > (ulong)s->limit )
    outOf(s);

  if ( mmap(s->max, incr,
	    PROT_READ|PROT_WRITE, STACK_MAP_TYPE,
	    mapfd, 0L) != s->max )
    fatalError("Failed to map memory at 0x%x for %d bytes on fd=%d: %s\n",
	       s->max, incr, mapfd, OsError());

  DEBUG(1, Sdprintf("mapped %d bytes from 0x%x to 0x%x\n",
		    size_alignment, (unsigned) s->max, s->max + incr));
  s->max = addPointer(s->max, incr);
  considerGarbageCollect(s);
}


#ifdef NO_SEGV_HANDLING
void
ensureRoomStack(Stack s, int bytes)
{ while((char *)s->max - (char *)s->top < (int)bytes)
    mapOrOutOf(s);
}
#endif

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
unmap() returns all memory resources of a stack that are  no  longer  in
use to the OS.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

static void
unmap(Stack s)
{ caddress top  = (s->top > s->min ? s->top : s->min);
  caddress addr = (caddress) align_size((long) top + size_alignment);

  if ( addr < s->max )
  { if ( munmap(addr, (char *)s->max - (char *)addr) != 0 )
      fatalError("Failed to unmap memory: %s", OsError());
    s->max = addr;
  }
}


#ifdef O_SAVE

static void
deallocateStack(Stack s)
{ long len = (unsigned long)s->max - (unsigned long)s->base;

  if ( len > 0 && munmap(s->base, len) != 0 )
    fatalError("Failed to unmap memory: %s", OsError());
}


void
deallocateStacks(void)
{ deallocateStack((Stack) &LD->stacks.local);
  deallocateStack((Stack) &LD->stacks.global);
  deallocateStack((Stack) &LD->stacks.trail);
  deallocateStack((Stack) &LD->stacks.argument);
}


bool
restoreStack(Stack s)
{ caddress max;
  long len;
  struct stat statbuf;

  if ( mapfd < 0 || fstat(mapfd, &statbuf) == -1 )
  { mapfd = get_map_fd();
    base_alignment = size_alignment = getpagesize();
  }

  max = (caddress) align_size((long) s->top + 1);
  len = max - (caddress) s->base;

  if ( mmap(s->base, len,
	    PROT_READ|PROT_WRITE, STACK_MAP_TYPE,
	    mapfd, 0L) != s->base )
    fatalError("Failed to map memory at 0x%x for %d bytes on fd=%d: %s\n",
	       s->base, len, mapfd, OsError());

  s->max = max;
  DEBUG(0, Sdprintf("mapped %d bytes from 0x%x\n", len, (unsigned) s->base));
  succeed;
}
#endif /*O_SAVE*/

#endif /* MMAP_STACK */

#if O_SHARED_MEMORY
#include <sys/stat.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#if gould
#define S_IRUSR SHM_R
#define S_IWUSR SHM_W
#endif
#if mips
struct pte { long pad };		/* where is the real one? */
#include <sys/param.h>
#endif

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Shared memory based MMU controlled stacks are a bit  more  tricky.   The
main  problem is that shared memory segments are scares resources.  Upto
a certain limit, each time the size of the stack is doubled.  Afterwards
the stack grows in fixed segments  of  size  s->segment_initial  *  2  ^
s->segment_double.   These  parameters  may  vary  from  stack to stack,
suiting the caracteristics of the stack and of the OS limits on  virtual
address space and number of shared memory segnments.  See pl-incl.h

The  shared  memory  segments  are  created,  mapped   and   immediately
afterwards  freed.   According  to  the documentation they actually will
live untill they are unmapped by the last process.  Immediately  freeing
them  avoids the burden to do this on exit() and ensures these resources
are freed, also if SWI-Prolog crashes.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

#if O_SHM_ALIGN_FAR_APART

#define min(a, b) ((a) < (b) ? (a) : (b))

static long
new_stack_size(s)
Stack s;
{ long size  = s->top - s->base;
  long free  = size / s->segment_initial;
  long limit = diffPointers(s->limit, s->base);

  if ( free > s->segment_double ) free = s->segment_double;
  else if ( free < 1 )            free = 1;
  
  size = align_size(size + free * s->segment_initial);  

  if ( size > limit )
    size = limit;

  return size;
}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
resize_segment(s, n, size)
  Resize segment n of stack s to get size size.  The base address of the
  segement is assumed to be correct.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

static void
resize_segment(s, n, size)
Stack s;
int n;
long size;
{ if ( s->segments[n].size != size )
  { int id = -1;
    char *addr;

    if ( size > 0 )
    { if ( (id=shmget(IPC_PRIVATE, size, S_IRUSR|S_IWUSR)) < 0 )
	fatalError("Failed to create shared memory object: %s", OsError());
      if ( (addr = shmat(id, 0, 0)) < 0 )
	fatalError("Failed to attach shared memory segment: %s", OsError());
      memcpy(s->segments[n].base, addr, min(size, s->segments[n].size));
      if ( shmdt(addr) < 0 )
      	fatalError("Failed to detach shared memory segment: %s", OsError());
    }    

    if ( s->segments[n].size > 0 )
      if ( shmdt(s->segments[n].base) < 0 )
      	fatalError("Failed to detach shared memory segment: %s", OsError());
    
    if ( id >= 0 )
    { DEBUG(0, Sdprintf("Attach segment of size %ld at 0x%x\n",
		      size, s->segments[n].base));
      if ( shmat(id, s->segments[n].base, 0) != s->segments[n].base )
      	fatalError("Failed to attach shared memory segment at 0x%x: %s",
		   s->segments[n].base, OsError());
      
      if ( shmctl(id, IPC_RMID, NULL) < 0 )
	fatalError("Failed to release shared memory object: %s", OsError());
    }

    s->segments[n].size = 0;
  }
}


void
mapOrOutOf(Stack s)
{ long new_size = new_stack_size(s);
  int  top_segment = new_size / base_alignment;
  int  n;

  DEBUG(1, Sdprintf("Expanding %s stack to %ld\n", s->name, new_size));

  for(n=0; n < top_segment; n++)
    resize_segment(s, n, base_alignment);

  resize_segment(s, n, new_size % base_alignment);

  for(n++; s->segments[n].size > 0; n++ )
    resize_segment(s, n, 0L);

  s->max = s->base + new_size;
  considerGarbageCollect(s);
}


static void
unmap(Stack s)
{ if ( new_stack_size(s) < s->max - s->base )
    mapOrOutOf(s);
}

#else /* O_SHM_ALIGN_FAR_APART */

void
mapOrOutOf(Stack s)
{ int id;
  char *rval;
  long len;
  caddress addr;

  len  = (s->segment_top <= s->segment_double
	  		? s->segment_initial << (s->segment_top)
	  		: s->segment_initial << s->segment_double);
  addr = s->segments[s->segment_top].base;

  if ( (id=shmget(IPC_PRIVATE, len, S_IRUSR|S_IWUSR)) < 0 )
  { if ( errno == EINVAL )
      fatalError("Kernel is not configured with option IPCSHMEM (contact a guru)");
    fatalError("Failed to create shared memory object: %s", OsError());
  }

  if ( (rval = shmat(id, addr, 0)) != (char *) addr )
    fatalError("Failed to map memory at %ld: %s\n", addr, OsError());

  if ( shmctl(id, IPC_RMID, NULL) < 0 )
    fatalError("Failed to release shared memory object: %s", OsError());

  s->segment_top++;
  s->max = s->segments[s->segment_top].base = addr+len;
  considerGarbageCollect(s);
}


static void
unmap(Stack s)
{ while( s->segment_top > 0 && s->segments[s->segment_top-1].base > s->top )
  { s->segment_top--;
    if ( shmdt(s->segments[s->segment_top].base) < 0 )
      fatalError("Failed to unmap: %s\n", OsError());
    s->max = s->segments[s->segment_top].base;
  }
}

#endif /* O_SHM_ALIGN_FAR_APART */
#endif /* O_SHARED_MEMORY */

#ifdef SIGNAL_HANDLER_PROVIDES_ADDRESS
static bool
expandStack(Stack s, caddress addr)
{ if ( addr < s->max || addr >= addPointer(s->limit, STACK_SEPARATION) )
    fail;				/* outside this area */

  if ( addr <= s->max + STACK_SEPARATION*2 )
  { if ( addr < s->limit )
    { DEBUG(1, Sdprintf("Expanding %s stack\n", s->name));
      mapOrOutOf(s);

      succeed;
    }

    outOf(s);   
  }

  fail;
}
#endif /*O_SHARED_MEMORY*/

#ifdef HAVE_VIRTUAL_ALLOC

#undef FD_ZERO
#undef FD_ISSET
#undef FD_SET
#include <windows.h>
#undef small

static void
mapOrOutOf(Stack s)
{ ulong incr;

  if ( s->top > s->max )
    incr = ROUND(((ulong)s->top - (ulong)s->max), size_alignment);
  else
    incr = size_alignment;

  if ( addPointer(s->max, incr) > s->limit )
    outOf(s);

  if ( VirtualAlloc(s->max, incr,
		    MEM_COMMIT, PAGE_READWRITE ) != s->max )
    fatalError("VirtualAlloc() failed at 0x%x for %d bytes: %d\n",
	       s->max, incr, GetLastError());

  DEBUG(1, Sdprintf("mapped %d bytes from 0x%x to 0x%x\n",
		    incr, (unsigned) s->max,
		    (ulong) s->max + size_alignment));

  s->max = addPointer(s->max, incr);
  considerGarbageCollect(s);
}


#ifdef NO_SEGV_HANDLING
void
ensureRoomStack(Stack s, int bytes)
{ while((char *)s->max - (char *)s->top < (int)bytes)
    mapOrOutOf(s);
}
#endif

static void
unmap(Stack s)
{ caddress top  = (s->top > s->min ? s->top : s->min);
  caddress addr = (caddress) align_size((long) top + size_alignment);

  if ( addr < s->max )
  { if ( !VirtualFree(addr, (ulong)s->max - (ulong)addr, MEM_DECOMMIT) )
      fatalError("Failed to unmap memory: %d", GetLastError());
    s->max = addr;
  }
}


#define MAX_VIRTUAL_ALLOC (100 MB)
#define SPECIFIC_INIT_STACK 1

static void
initStacks(long local, long global, long trail, long argument)
{ SYSTEM_INFO info;
  int large = 0;			/* number of `large' stacks */
  ulong base;				/* allocation base */
  ulong totalsize;			/* total size to allocate */

  GetSystemInfo(&info);
  size_alignment = info.dwPageSize;
  base_alignment = size_alignment;
/*base_alignment = info.dwAllocationGranularity;*/

  local    = (long) align_size(local);	/* Round up to page boundary */
  global   = (long) align_size(global);
  trail    = (long) align_size(trail);
  argument = (long) align_size(argument);

  if ( local    == 0 ) large++;		/* find dynamic ones */
  if ( global   == 0 ) large++;
  if ( trail    == 0 ) large++;
  if ( argument == 0 ) large++;

  if ( large )
    totalsize = MAX_VIRTUAL_ALLOC;
  else
    totalsize = local + global + trail + argument + 4 * STACK_SEPARATION;

  if ( !(base = (ulong) VirtualAlloc(NULL, totalsize,
				     MEM_RESERVE, PAGE_READWRITE)) )
    fatalError("Failed to allocate stacks for %d bytes: %d",
	       totalsize, GetLastError());

  if ( large )
  { ulong space = totalsize -
           ( align_base(local + STACK_SEPARATION) +
	     align_base(global + STACK_SEPARATION) +
	     align_base(trail + STACK_SEPARATION) +
	     align_base(argument) );
    ulong large_size = ((space / large) / base_alignment) * base_alignment;

    if ( large_size < STACK_MINIMUM )
      fatalError("Can't fit requested stack sizes in address space");
    DEBUG(1, Sdprintf("Large stacks are %ld\n", large_size));

    if ( local    == 0 ) local    = large_size;
    if ( global   == 0 ) global   = large_size;
    if ( trail    == 0 ) trail    = large_size;
    if ( argument == 0 ) argument = large_size;
  }

#define INIT_STACK(name, print, limit, minsize) \
  DEBUG(1, Sdprintf("%s stack at 0x%x; size = %ld\n", print, base, limit)); \
  init_stack((Stack) &LD->stacks.name, print, (caddress) base, limit, minsize); \
  base += limit + STACK_SEPARATION; \
  base = align_base(base);
#define K * 1024

  INIT_STACK(global,   "global",   global,   16 K);
  INIT_STACK(local,    "local",    local,    8 K);
  INIT_STACK(trail,    "trail",    trail,    8 K);
  INIT_STACK(argument, "argument", argument, 1 K);

#ifndef NO_SEGV_HANDLING
  pl_signal(SIGSEGV, (handler_t) segv_handler);
#endif
}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Reset the stacks after an abort
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

void
resetStacks()
{ emptyStacks();

#ifndef NO_SEGV_HANDLING
  pl_signal(SIGSEGV, (handler_t) segv_handler);
#endif
  trimStacks();
}

#endif /*HAVE_VIRTUAL_ALLOC*/

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
This the the signal handler for segmentation faults if we are using  MMU
controlled  stacks.   The  only  argument  we  are  interested in is the
address of the segmentation fault.  SUN provides this via  an  argument.
If   your   system   does   not   provide   this  information,  set  the
SIGNAL_HANDLER_PROVIDES_ADDRESS flag.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

#ifndef NO_SEGV_HANDLING
static RETSIGTYPE
#ifdef SIGNAL_HANDLER_PROVIDES_ADDRESS
segv_handler(int sig, int type, SignalContext scp, char *addr)
#else
segv_handler(int sig)
#endif
{ Stack stacka = (Stack) &LD->stacks;
  int i;

#ifndef SIGNAL_HANDLER_PROVIDES_ADDRESS
  int mapped = 0;

  DEBUG(1, Sdprintf("Page fault.  Free room (g+l+t) = %ld+%ld+%ld\n",
		    roomStack(global), roomStack(local), roomStack(trail)));

  for(i=0; i<N_STACKS; i++)
  { long r = (ulong)stacka[i].max - (ulong)stacka[i].top;

    if ( r < size_alignment )
    { DEBUG(1, Sdprintf("Mapped %s stack (free was %d)\n", stacka[i].name, r));
      mapOrOutOf(&stacka[i]);
      mapped++;
    }
  }

  if ( mapped )
  {
#ifndef BSD_SIGNALS
    signal(SIGSEGV, (handler_t) segv_handler);
#endif
    return;
  }

#else /*SIGNAL_HANDLER_PROVIDES_ADDRESS*/

  DEBUG(1, Sdprintf("Page fault at %ld (0x%x)\n", (long) addr, (unsigned) addr));
  for(i=0; i<N_STACKS; i++)
    if ( expandStack(&stacka[i], addr) )
    {
#ifndef BSD_SIGNALS
      signal(sig, (handler_t) segv_handler);
#endif
      return;
    }
#endif /*SIGNAL_HANDLER_PROVIDES_ADDRESS*/

#ifdef SIGNAL_HANDLER_PROVIDES_ADDRESS
  deliverSignal(sig, type, scp, addr);
#else
  deliverSignal(sig, 0, 0, NULL);	/* for now ... */
#endif
}

#endif /*NO_SEGV_HANDLING*/

static void
init_stack(Stack s, char *name, caddress base, long limit, long minsize)
{ s->name      = name;
  s->base      = s->max = s->top = base;
  s->limit     = addPointer(base, limit);
  s->min       = (caddress)((ulong)s->base + minsize);
  s->gced_size = 0L;			/* size after last gc */
  gcPolicy(s, GC_FAST_POLICY);
#if O_SHARED_MEMORY
#if O_SHM_ALIGN_FAR_APART
{ int n;

  s->segment_initial = 32 * 1024;
  s->segment_double  = 20;
  for(n=0; n < MAX_STACK_SEGMENTS; n++)
  { s->segments[n].size = 0;
    s->segments[n].base = s->base + base_alignment * n;
  }
}
#else /* O_SHM_ALIGN_FAR_APART */
  s->segment_top     = 0;
  s->segment_initial = 32 * 1024;
  s->segment_double  = 5;
  s->segments[0].base = base;
#endif /* O_SHM_ALIGN_FAR_APART */
#endif /* O_SHARED_MEMORY */

  DEBUG(1, Sdprintf("%-8s stack from 0x%08x to 0x%08x\n",
		    s->name, (ulong)s->base, (ulong)s->limit));

  while(s->max < s->min)
    mapOrOutOf(s);
}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
initStacks() initialises the stacks structure,  thus  assigning  a  base
address,  a limit and a name to each of the stacks.  Finally it installs
a signal handler for handling  segmentation  faults.   The  segmentation
fault handler will actually create and expand the stacks on segmentation
faults.

The big problem is finding a safe   area  for the stacks. Currently, the
system tries to find an area as far   as possible from the heap, growing
downwards  if  it  can  determine  the    top  of  the  heap-area  using
topOfHeap(). If it cannot, it will work from the current top of the heap
as returned by sbrk(0).
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

#ifdef FORCED_MALLOC_BASE
#undef MMAP_MAX_ADDRESS
#undef MMAP_MIN_ADDRESS
#define MMAP_MAX_ADDRESS (FORCED_MALLOC_BASE + 64 MB)
#define MMAP_MIN_ADDRESS (FORCED_MALLOC_BASE + 16 MB)
#endif

#ifndef SPECIFIC_INIT_STACK

static void
initStacks(long local, long global, long trail, long argument)
{ int large = 0;
  ulong base, top, space, large_size, min_space;

  size_alignment = getpagesize();
#ifdef MMAP_STACK
  base_alignment = size_alignment;
  mapfd  = get_map_fd();
#endif
#if O_SHARED_MEMORY
  base_alignment = SHMLBA;
  DEBUG(0, Sdprintf("Shared memory must be aligned to %d (0x%x) bytes\n",
		  base_alignment, base_alignment));
#endif

  local    = (ulong) align_size(local);	/* Round up to page boundary */
  global   = (ulong) align_size(global);
  trail    = (ulong) align_size(trail);
  argument = (ulong) align_size(argument);

  min_space = align_base(1) +
	      align_base(local + STACK_SEPARATION) +
	      align_base(global + STACK_SEPARATION) +
	      align_base(trail + STACK_SEPARATION) +
	      align_base(argument);

  if ( local    == 0 ) large++;		/* find dynamic ones */
  if ( global   == 0 ) large++;
  if ( trail    == 0 ) large++;
  if ( argument == 0 ) large++;

  if ( (top = topOfHeap()) > 0L && !GD->options.heapSize )
  { if ( large > 0 )			/* we have dynamic stacks */
    { base = heap_base;
      space = top - base;
      space -= min_space;
      large++;				/* heap as well */
      large_size = ((space / large+1) / base_alignment) * base_alignment;
      if ( large_size > 64 MB )
	large_size = 64 MB;
      if ( large_size < STACK_MINIMUM )
	fatalError("Can't fit requested stack sizes in address space");
      DEBUG(1, Sdprintf("Large stacks are %ld\n", large_size));

      if ( local    == 0 ) local    = large_size;
      if ( global   == 0 ) global   = large_size;
      if ( trail    == 0 ) trail    = large_size;
      if ( argument == 0 ) argument = large_size;
    }

    base = top - (align_base(1) +
		  align_base(local + STACK_SEPARATION) +
		  align_base(global + STACK_SEPARATION) +
		  align_base(trail + STACK_SEPARATION) +
		  align_base(argument));
    base = align_base_down(base);
  } else				/* we don't know the top */
  { ulong maxdata = dataLimit();

    if ( !GD->options.heapSize )
    { if ( maxdata )
      { large_size = align_base_down((maxdata-min_space)/(large+1));
	large_size = max(large_size, 64 MB);
      } else
	large_size = 64 MB;
	
      GD->options.heapSize = large_size;
    } else
    { large_size = align_base_down((maxdata-min_space)/(large+1));
      large_size = max(large_size, 64 MB);
    }

#ifdef MMAP_MIN_ADDRESS
    base = MMAP_MIN_ADDRESS;
#else
    base = (ulong) align_base((ulong)sbrk(0) + GD->options.heapSize);
#endif

    if ( large > 0 )
    { DEBUG(1, Sdprintf("Large stacks are %ld\n", large_size));
  
      if ( local    == 0 ) local    = large_size;
      if ( global   == 0 ) global   = large_size;
      if ( trail    == 0 ) trail    = large_size;
      if ( argument == 0 ) argument = large_size;
    }
  }

#define INIT_STACK(name, print, limit, minsize) \
  DEBUG(1, Sdprintf("%s stack at 0x%x; size = %ld\n", print, base, limit)); \
  init_stack((Stack) &LD->stacks.name, print, (caddress) base, limit, minsize); \
  base += limit + STACK_SEPARATION; \
  base = align_base(base);
#define K * 1024

  INIT_STACK(global,   "global",   global,   16 K);
  INIT_STACK(local,    "local",    local,    8 K);
  INIT_STACK(trail,    "trail",    trail,    8 K);
  INIT_STACK(argument, "argument", argument, 1 K);

  assert(top == 0L || (ulong)aLimit <= top);

#ifndef NO_SEGV_HANDLING
  pl_signal(SIGSEGV, (handler_t) segv_handler);
#endif
}

void
resetStacks()
{ emptyStacks();

#ifndef NO_SEGV_HANDLING
  pl_signal(SIGSEGV, (handler_t) segv_handler);
#endif
  trimStacks();
}


#endif /*SPECIFIC_INIT_STACK*/

		/********************************
		*     STACK TRIMMING & LIMITS   *
		*********************************/

static void
gcPolicy(Stack s, int policy)
{ s->gc = ((s == (Stack) &LD->stacks.global ||
	    s == (Stack) &LD->stacks.trail) ? TRUE : FALSE);
  if ( s->gc )
  { s->small  = SMALLSTACK;
    s->factor = 3;
    s->policy = policy;
  } else
  { s->small  = 0;
    s->factor = 0;
    s->policy = 0;
  }
}


word
pl_trim_stacks()
{ trimStacks();

  gcPolicy((Stack) &LD->stacks.global, GC_FAST_POLICY);
  gcPolicy((Stack) &LD->stacks.trail,  GC_FAST_POLICY);

  succeed;
}


#else /* O_DYNAMIC_STACKS */

		/********************************
		*    SIMPLE STACK ALLOCATION    *
		*********************************/

forwards void init_stack(Stack, char *, long, long, long);

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
On systems that do not allow us to get access to the MMU (or that do not
have an MMU)  the  stacks  have  fixed  size  and  overflow  checks  are
implemented  in  software.   The stacks are allocated using malloc(). If
you malloc() does not allow you to get more than 64K bytes in one go you
better start looking for another Prolog system (IBM-PC  is  an  example:
why does IBM bring computers on the marked that are 10 years out-of-date
at the moment of announcement?).
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

word
pl_trim_stacks()
{ succeed;
}


word
pl_stack_parameter(term_t name, term_t key, term_t old, term_t new)
{ atom_t a, k;
  Stack stack = NULL;
  long *value = NULL;

  if ( PL_get_atom(name, &a) )
  { if ( a == ATOM_local )
      stack = (Stack) &LD->stacks.local;
    else if ( a == ATOM_global )
      stack = (Stack) &LD->stacks.global;
    else if ( a == ATOM_trail )
      stack = (Stack) &LD->stacks.trail;
    else if ( a == ATOM_argument )
      stack = (Stack) &LD->stacks.argument;
  }
  if ( !stack )
    return warning("stack_parameter/4: unknown stack");

  if ( PL_get_atom(key, &k) )
  { if ( k == ATOM_min_free )
      value = &stack->minfree;
  }
  if ( !value )
    return warning("stack_parameter/4: unknown key");

  return setLong(value, "stack_parameter/4", old, new);
}


static void
init_stack(Stack s, char *name, long size, long limit, long minfree)
{ if ( s->base == NULL )
  { fatalError("Not enough core to allocate stacks");
    return;
  }

  s->name 	= name;
  s->top	= s->base;
  s->limit	= addPointer(s->base, limit);
  s->minfree	= minfree;
  s->max	= (char *)s->base + size;
  s->gced_size = 0L;			/* size after last gc */
  s->gc	       = ((s == (Stack) &LD->stacks.global ||
		   s == (Stack) &LD->stacks.trail) ? TRUE : FALSE);
  s->small     = (s->gc ? SMALLSTACK : 0);
}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
On tos, malloc() returns a 2 byte  aligned  pointer.   We  need  4  byte
aligned  pointers.   Allocate() is patched for that and dumped states do
not exist.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

#if tos
#define MALLOC(p, n)    Allocate(n)
#else
#define MALLOC(p, n)	(!GD->dumped ? malloc(n) : realloc(p, n))
#endif

static void
initStacks(long local, long global, long trail, long arg)
{ long old_heap = GD->statistics.heap;
#if O_SHIFT_STACKS
  long itrail  = 32 K;
  long iglobal = 200 K;
  long ilocal  = 32 K;
#else
  long itrail  = trail;
  long iglobal = global;
  long ilocal  = local;
#endif

  gBase = (Word) MALLOC(gBase, iglobal + sizeof(word) +
			       ilocal + sizeof(struct localFrame) +
			       MAXARITY * sizeof(word));
  lBase = (LocalFrame)	addPointer(gBase, iglobal+sizeof(word));
  tBase = (TrailEntry)	MALLOC(tBase, itrail);
  aBase = (Word *)	MALLOC(aBase, arg);

  init_stack((Stack)&LD->stacks.global,	  "global",   iglobal, global, 100 K);
  init_stack((Stack)&LD->stacks.local,    "local",    ilocal,  local,   16 K);
  init_stack((Stack)&LD->stacks.trail,    "trail",    itrail,  trail,    8 K);
  init_stack((Stack)&LD->stacks.argument, "argument", arg,     arg,      0 K);

  GD->statistics.heap = old_heap;
}

void
resetStacks()
{ emptyStacks();
}

#endif /* O_DYNAMIC_STACKS */

void
trimStacks()
{
#ifdef O_DYNAMIC_STACKS
  unmap((Stack) &LD->stacks.local);
  unmap((Stack) &LD->stacks.global);
  unmap((Stack) &LD->stacks.trail);
  unmap((Stack) &LD->stacks.argument);
#endif /*O_DYNAMIC_STACKS*/

  LD->stacks.global.gced_size = usedStack(global);
  LD->stacks.trail.gced_size  = usedStack(trail);
}
