/* $Copyright:	$
 * Copyright (c) 1984, 1985, 1986, 1987, 1988, 1989, 1990 
 * Sequent Computer Systems, Inc.   All rights reserved.
 *  
 * This software is furnished under a license and may be used
 * only in accordance with the terms of that license and with the
 * inclusion of the above copyright notice.   This software may not
 * be provided or otherwise made available to, or used by, any
 * other person.  No title to or ownership of the software is
 * hereby transferred.
 */

#ident "@(#)$Header: shbrk.c 1.3 90/01/26 $"

/*
 * shbrk.c
 *	Parallel run-time basic shared memory allocator.
 *
 * TODO:
 *	Worry about taking signal in sbrk()/shmbrk() (use process local
 *		signal stack; only really issue for multi-tasker).
 */

#define	ADDR int
#ifdef	DEBUG
#define	STATIC
#else	/* !DEBUG */
#define	STATIC static
#endif	/* !DEBUG */

#include <sys/types.h>
#include <sys/errno.h>
#include <sys/file.h>
#include <sys/mman.h>
#include <sys/signal.h>
#include <sys/vmparam.h>

#include "parallel.h"
#include "microtask.h"
#include "parc.h"
#include "team.h"
/*
 *	define m_numprocs here to avoid cycle in lorder
 */
shared	int	m_numprocs = -1;

/*
 * Module local variables.
 */

private	int _priv_stack_limit;			/* size limit for priv stack */
private	STATIC	ADDR myshbrk;		/* process local max sbrk */
shared	STATIC	ADDR curshbrk;		/* global for all processes */
shared	STATIC	slock_t	shbrk_lock = L_UNLOCKED;

ADDR _shared_end;

extern _stack_incr;
extern _stack_hole;
extern _stack_limit;
int _stack_limit_worker;
extern private int m_myid;
extern shared char sshdata;
/*
 * round an address down to the beginning of a STACK_INCR boundary
 */
#define	STACK_ROUND(a)	(ADDR)((int)(a) & (~(_stack_incr - 1)))
#define	STACK_ROUND_UP(a) (ADDR)((int)(a + _stack_incr) & (~(_stack_incr - 1)))

/*
 * stack_top and stack_base bound the shared parent stack.  Require that
 *	stack pointer be in the interval:
 *
 *		[stack_base,stack_top)
 *
 */
shared STATIC ADDR stack_top[MAXPROCS];		/* shared stack top address */
shared STATIC ADDR stack_base[MAXPROCS];	/* shared stack lower limit */
private STATIC ADDR my_stack0;	/* lowest mapped address of the stack 0 */

/*
 * _init_shbrk()
 *	Initialize basic shared-memory allocator.
 *
 * Called from _ppinit().
 */

STATIC void	segvh();
static struct sigaction act;
private STATIC int segvstack[512];
private static struct sigstack sigstruct;
int sigstack();
extern ADDR getenv();

_init_shbrk()
{
	extern int _do_shared_stack;

	act.sa_handler = segvh;
	sigfillset(&act.sa_mask);
	sigaddset(&act.sa_mask,SIGCHLD);
	act.sa_flags = SA_ALTSTACK;
	sigstruct.ss_sp = (caddr_t)&segvstack[511];
	sigstruct.ss_onstack = 0;

	
	/*
	 * Set up SIGSEGV handler; catch signal and grow shared-data
	 * and shared stack if appropriate.
	 */

	(void)sigstack(&sigstruct, (struct sigstack *)0);
	(void)sigaction(SIGSEGV, &act, (struct sigaction *)NULL);

	/*
	 * Initialize state variables.
	 */

	if (!curshbrk) curshbrk = myshbrk = _shared_end;

	if (curshbrk >= ((ADDR) MAXADDR) - _priv_stack_limit - _stack_hole){
		printf("ppinit: shared break address too big\n");
		errno = ENOEXEC;
		return(-1);
	}
	return(0);
}

/*
 * shsbrk()
 *	shared-memory sbrk(); create new shared space, return start address.
 */

char *
shsbrk(incr)
	int	incr;
{
	ADDR	val;
	register slock_t *spin_ptr = &shbrk_lock;

	if (incr == 0)
		return((char *)curshbrk);	/* note: can race with others! */

	if (incr < 0) {			/* can't shrink */
		errno = EINVAL;
		return((char *) -1);
	}

	/*
	 * The lock has been reduced to the section that actually allocates
	 * the memory by doing a fetch-and-add to curshbrk. The mmaping
	 * can occur later, and is not a critical section.
	 *
	 * This eliminates segv races without turning off the segv handler.
	 */

	S_LOCK(spin_ptr);
	/*
	 *	stop before running into the private stacks at top of space
	 */
	val = curshbrk;
	if (val + incr < ((ADDR) MAXADDR) - _priv_stack_limit - _stack_hole)
		curshbrk += incr;
	else	val = (ADDR) -1;
	S_UNLOCK(spin_ptr);

	if (mapheap()) return((char *) -1);
	return((char *)val);
}

#define	ALLOCASIZE (27*4)
/*
 * _m_find_shstk()
 *	Find the stack for process i, or create one.
 *	Waste ALLOCASIZE bytes, so that alloca can be guaranteed to have
 *	some bytes to copy before running off the top of the stack.
 */
ADDR
_m_find_shstk(i)
	int i;
{
	ADDR	val;

	val = stack_top[i];
	munmap(val - _stack_limit_worker - _stack_hole, _stack_hole);
	return(val - ALLOCASIZE);
}
/*
 * _m_init_worker_stacks
 *	Make sure we have m_numprocs -1 worker stacks allocated
 */
ADDR
_m_init_worker_stacks()
{
	register slock_t *spin_ptr = &shbrk_lock;
	register int i;
	register ADDR j;
	extern int _stk_fd;

	if (stack_top[m_numprocs-1]) return;
	S_LOCK(spin_ptr);
	if (mapheap()) errno = ENOMEM;
	for (i = 1; i < m_numprocs; i++) if (!stack_top[i]) break;
	j = (ADDR) PGRND(curshbrk);
	if (j >= ((ADDR)MAXADDR) - _priv_stack_limit - _stack_hole) {
		errno = ENOMEM;
		S_UNLOCK(spin_ptr);
	}
	myshbrk = curshbrk = j + (m_numprocs - i) *
		(_stack_hole + _stack_limit_worker);
	if (MMAP(_stk_fd, (caddr_t) j, myshbrk - j,
		j - stack_base[0]) == -1) {
		errno = ENOMEM;
	}
	while (i < m_numprocs) {
		stack_base[i] = j = j + _stack_hole;
		stack_top[i] = j = j + _stack_limit_worker;
		i++;
	}

	S_UNLOCK(spin_ptr);
}

/*
 * shbrk()
 *	Add shared address space, analogous to brk().
 *
 * Just front-end to lshbrk() -- lock curshbrk and try to grow.
 */

shbrk(newbrk)
	ADDR	newbrk;
{
	register slock_t *spin_ptr = &shbrk_lock;

	if (newbrk >= ((ADDR)MAXADDR) - _priv_stack_limit - _stack_hole) {
		errno = EINVAL;
		return(-1);
	}

	S_LOCK(spin_ptr);

	if (curshbrk < newbrk) curshbrk = newbrk;

	S_UNLOCK(spin_ptr);

	if (mapheap()) return(-1);

	return(0);
}

/*
 *	re-mmap heap area if necessary
 */

STATIC
mapheap()
{
	register ADDR	cur;			/* actual brk (rounded) */
	register ADDR	new;			/* requested brk (rounded) */
	register ADDR	curshbrk_copy = curshbrk; /* new brk, (rounded) */
	extern int _stk_fd, _sh_start_;


	/*
	 * Growing: if no actual increase (rounding/etc), just adjust
	 * (self) myshbrk.
	 */

	cur = (ADDR) PGRND(myshbrk);
	new = (ADDR) PGRND(curshbrk_copy);
	if (_do_shared_stack) {
	    if (new > cur && MMAP(_stk_fd, cur, new - cur,
			(char *) cur - stack_base[0]) < 0) {
		errno = ENOMEM;
		return(-1);
	    }
	} else {
	    if (new > cur && MMAP(_shm_fd, cur, new - cur,
			(char *) cur - &sshdata) < 0) {
		errno = ENOMEM;
		return(-1);
	}   }

	myshbrk = curshbrk_copy;

	return(0);			/* success! */
}

/*
 * _init_shstk()
 *	Initialize basic shared-memory allocator.
 *
 * Called from _ppinit().
 *	return pointer to the bottom of the shared stack.  This
 *	holds the stack context for the EXIT, RET instructions
 *	out of ppinit().
 */

ADDR
_init_shstk(ctxt_bottom, ctxt_top)
ADDR ctxt_bottom;
ADDR ctxt_top;
{
	int copy_len;
	ADDR new_sp;

	extern int _stk_fd;
	/*extern	shared char _shend_;*/

	if (_stack_incr < 0) _stack_incr = 0x10000;
	_stack_incr &= ~(getpagesize() - 1);
	if (!_stack_hole) _stack_hole = _stack_incr;
	_stack_limit = STACK_ROUND_UP(_stack_limit);
	if (!_stack_limit_worker) _stack_limit_worker = _stack_limit >> 4;
	_stack_limit_worker = STACK_ROUND_UP(_stack_limit_worker);

	/*
	 * initialize stack bounds
	 */
	stack_base[0] = STACK_ROUND_UP(_shared_end + _stack_hole);
	stack_top[0] = stack_base[0] + _stack_limit;
	myshbrk = curshbrk = stack_top[0];
	my_stack0 = stack_top[0] - _stack_incr;

	/*
	 * check for overflow into private stack area
	 * By definition at this point, there's no possibiltiy for overflow
	 * into shared data region.
	 */
	if (stack_top[0] > STACK_ROUND(MAXADDR - _priv_stack_limit +
		_stack_hole)){
		printf("not enough address space for stack\n");
		errno = ENOMEM;
		return((ADDR )-1);
	}

	/*
	 * actually allocate some stack segment space.
	 */
	if (MMAP(_stk_fd, (caddr_t) stack_top[0] - _stack_incr,
		_stack_incr, _stack_limit - _stack_incr) == -1) {
		errno = ENOMEM;
		return((ADDR )-1);
	}

	/*
	 * now, copy context from the private stack onto the shared stack
	 * want to copy ONLY the PROCEDURE CALL context for ppinit() that
         * will be left on the current stack after this function exits.
	 * This is also the place we want the new stack pointer to be when
	 * ppinit() switches to the shared stack.
	 */

	copy_len = ctxt_top - ctxt_bottom;
	new_sp = stack_top[0] - copy_len;
#ifdef	DEBUG
	printf("copy_len %x new_sp %x ctxt_top %x ctxt_bottom %x\n",
		copy_len, new_sp, ctxt_top, ctxt_bottom);
#endif	/* DEBUG */
	memcpy((char *)new_sp,(char *) ctxt_bottom, (unsigned int)copy_len);
	return(new_sp);
}

/*
 * segvh()
 *	SIGSEGV handler.
 *
 * Check for growth.  If growth succeeded, just return.  Otherwise, replace
 * handler and retry (this should core-dump).
 *
 */
/*ARGSUSED*/
STATIC void
segvh(sig)
int sig;
{
	int sp, code, fa;

	if(sigcontext(SF_SP, &sp) == -1)
		(void) sigset(SIGSEGV, SIG_DFL);
	if(sigcontext(SF_SEGVCODE, &fa) == -1)
		(void) sigset(SIGSEGV, SIG_DFL);
	if(sigcontext(SF_CODE, &code) == -1)
		(void) sigset(SIGSEGV, SIG_DFL);
	_check_segv(sig, code, sp, fa);
}
/*
 * Check to see if the segv results in growth, or is a failure.
 *	0 return means failure.
 *	1 return means growth occurred.
 *
 * This is meant to be called when a SIGSEGV has occurred, and further
 * SIGSEGV's are blocked.  Also, this should be invoked on a process-private
 * stack.
 *
 * add code to deal with mapping/growing the shared stack area.
 * SIGCODESLOP >= the amount of stack space needed to return from the signal
 */
#define	SIGCODESLOP 64

/*ARGSUSED*/
_check_segv(sig, code, sp, fa)
int sig, code, sp;
register ADDR fa;
{
	extern int _do_shared_stack;
	int i;

#ifdef	DEBUG
	printf("check_segv fa %x\n", fa);
#endif	/* DEBUG */
	/*
	 * a sig code of -1 indicates the signal originated
	 * from a kill() system call, rather than a real program
	 * fault
	 */
	if (code == (ADDR )-1) {
		(void) sigset(SIGSEGV, SIG_DFL);
		(void) kill(getpid(), SIGSEGV);
		return(0);
	}

	if (_do_shared_stack) {
		/*
		 *	Use sp at segv time if process 0
		 *	We need a good stack to return to
		 */
		i = fa;
		if (!m_myid && sp < i) i = sp - SIGCODESLOP;
		/*
		 * have we overflowed our stack?
		 * if so, we won't be able to unwind out
		 * of the current signal handler.  So,
		 * we should deliver a new signal on
		 * our current stack.  This signal
		 * handler should then kill off the kids.
		 */

		if (i < stack_base[m_myid] &&
			i >= stack_base[m_myid] - _stack_hole) {
#define	STKMSG "limit stacksize exceeded\n"
			write(2,STKMSG, sizeof(STKMSG));
			(void) kill(getpid(), SIGILL);
		}
		/*
		 *	check for unmapped part of stack 0
		 */
		if (i > stack_base[0] && i < my_stack0) {
			i = STACK_ROUND(i - stack_base[0]) + stack_base[0];
			if (MMAP(_stk_fd, (caddr_t) i, my_stack0 - i,
				i - stack_base[0]) == -1) {
#define	STKMMAP "shared stack extension failed\n"
				write(2,STKMMAP, sizeof(STKMMAP));
				(void) kill(getpid(), SIGILL);
			}
			my_stack0 = i;
			return(1);
		}
	}

	/*
	 * Fall through and see if the shared heap needs growing
	 *
	 * If process doesn't have all mapped, map it and retry.
	 * Otherwise turn off SIGSEGV handling.
	 *
	 * Return retries instruction, will die for real if turn off
	 * signal handling here.
	 */

	if (((ADDR )fa) < curshbrk && ((ADDR )fa) >= myshbrk) {
		if (!mapheap()) return(1);
	}
	(void) sigset(SIGSEGV, SIG_DFL);
	return(0);
}
