/* $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: c_interface.c 1.5 90/02/21 $"

#include <signal.h>
#include <errno.h>
#include "team.h"
#include "microtask.h"
#include "parallel.h"
#include "parc.h"

extern shared struct teamblock *m_myteam[];
extern private struct teamblock *m_teamblocks;
/* SPIN may be redefined to be nothing if compiled with -i */
#define SPIN(x) m_spin(x)

/* microtasking m_next routine */
int m_next()
{
	register struct teamblock *t;
	register int j;
	t = m_myteam[m_myid];
	Q_LOCK(&t->triplock);
	j = t->tripcounter;
	t->tripcounter -= 1;
	Q_UNLOCK(&t->triplock);
	return(t->initial_tripcounter - j + 1);
}
/* get number of processes */
int m_get_numprocs()
{
	return(m_myteam[m_myid]->initial_teamsize);
}
/* get number of process */
int m_get_myid()
{
	return(m_myid);
}
/* lock a loop-local lock */
m_lock()
{
	Q_LOCK(&m_myteam[m_myid]->userlock);
}
/* unlock a team-local lock */
m_unlock()
{
	Q_UNLOCK(&m_myteam[m_myid]->userlock);
}
/* do a barrier and let only process 0 return */
int m_single()
{
	register struct teamblock *team;
	register int j;
	team = m_myteam[m_myid];
	j = team->bar_trip;
	if (j != team->fork_trip) return(EINVAL);
	/* checkin */
	if (!atomic_decb_nonzero(
		&team->cells[team->pcells[m_myid].bar_cell_offset].count)) {
		if (!atomic_decb_nonzero(&team->cells[0].count)) {
			team->tripcounter = team->initial_tripcounter;
			team->bar_trip++;
			atomic_barrier_reset(&team->cells[0]);
		}
		atomic_barrier_reset(&team->cells[team->pcells[m_myid].bar_cell_offset]);
	}
	if (team->pcells[m_myid].in_team_id) {
		/* wait for fork cell to be tickled */
		while (j == team->fork_trip) {
			_m_choose_work();
			SPIN(&team->fork_trip);
		}
		set_return_address(team->fork_address);
	} else {
		/* team leader wait for barrier and continues */
		while (j == team->bar_trip) {
			_m_choose_work();
			SPIN(&team->bar_trip);
	}	}
	return(0);
}
/* do a barrier and release all processes to return address */
int m_multi()
{
	register struct teamblock *team;
	register int j;
	team = m_myteam[m_myid];
	team->fork_address = (int(*)()) my_return_address();
	/* let's go */
	team->fork_trip  = team->bar_trip;
	return(0);
}
static shared struct mfork_stuff {
	int nargs;
	void (*p)();
	char *aargs;
} mforkrec[30];

/*ARGSUSED*/
static m_work(fp)
struct mfork_stuff **fp;
{
	char *a;
	int n, i;
	i = -m_myteam[m_myid]->initial_tripcounter;
	n = mforkrec[i].nargs * 4;
	a = (char *) allocsp(n);
	memcpy((char *)a, (char *)mforkrec[i].aargs, (unsigned)n);
	(*mforkrec[i].p)();
	allocsp(-n);
}
/* wait for all processes to join idle team */
static m_init_mfork()
{
	while (m_myteam[0]->teamsize != m_numprocs || 
		Q_IS_LOCKED(&m_myteam[0]->teamlock));
}
/*VARARGS*/
int m_fork(p, args)
void (*p)();
char args;
{
	if (!m_myteam[0]) {
		m_fork1(0, m_init_mfork, 0);
		m_myteam[0]->initial_teamsize = m_numprocs;
	}
	if (mforkrec[m_myid].nargs = _nargs() - 1) {
		mforkrec[m_myid].p = p;
		mforkrec[m_myid].aargs = &args;
		/* <=0 tripcounter guarantees no nested dynamic help */
		m_fork1(-m_myid, m_work, 0);
		return(0);
	} else {
		m_fork1(0, p, 0);	/* no args--direct call */
		return(0);
	}
}
int m_pfork(slink, p, args)
int slink;	/* may be absent */
void (*p)();
char args;
{
	if (!m_myteam[0]) {
		m_fork1(0, m_init_mfork, 0);
		m_myteam[0]->initial_teamsize = m_numprocs;
	}
	if (slink) {
		mforkrec[m_myid].nargs = _nargs() - 1;
		mforkrec[m_myid].p = p;
		p = ((void (*)()) slink);
		mforkrec[m_myid].aargs = (char *) &p;
		m_fork1(-m_myid, m_work, 0);
		return(0);
	} else {
		mforkrec[m_myid].nargs = _nargs() - 2;
		if (mforkrec[m_myid].nargs) {
			mforkrec[m_myid].p = p;
			mforkrec[m_myid].aargs = &args;
			/* <=0 tripcounter guarantees no nested dynamic help */
			m_fork1(-m_myid, m_work, 0);
			return(0);
		} else {
			m_fork1(0, p, 0);	/* no args--direct call */
			return(0);
	}	}
}
/* built-in barrier routine */
void
m_sync()
{
	register struct teamblock *team;
	register int j;
	team = m_myteam[m_myid];
	j = team->fork_trip;
	/* checkin */
	if (!atomic_decb_nonzero(
		&team->cells[team->pcells[m_myid].bar_cell_offset].count)) {
		if (!atomic_decb_nonzero(&team->cells[0].count)) {
			team->tripcounter = team->initial_tripcounter;
			/* watch out--m_single interaction */
			team->bar_trip++;
			team->fork_trip++;
			atomic_barrier_reset(&team->cells[0]);
		}
		atomic_barrier_reset(&team->cells[team->pcells[m_myid].bar_cell_offset]);
	}
	/* everyone waits and continues */
	while (j == team->fork_trip) {
		_m_choose_work();
		SPIN(&team->fork_trip);
	}
}
/*
 * m_set_procs()
 *	Start processes, making them ready to help with the computation.
 *
 * Forks kids, remembers pid's.
 *
 * Returns 0 for all ok, else -1 (and errno).
 */

m_set_procs(nprocs)				/* C interface */
	register int nprocs;
{
	register int maxprocs;

	maxprocs = cpus_online() - 1;

	if (m_myteam[0] || (nprocs > MAXPROCS) || nprocs > maxprocs) {
		errno = EINVAL;
		return(-1);
	}

	m_numprocs = (nprocs > 0) ? nprocs : m_get_default();
	return(0);

}
m_park_procs()
{
	return(_m_park_procs());
}

/*
 * m_rele_procs()
 *	Relese processes from "parked" state -- ready to compute again.
 */

m_rele_procs()
{
	return(_m_rele_procs());
}

/*
 * m_kill_procs()
 *	Release slave processes to system, no longer needed.
 *
 * Also, wait for them to terminate (note this could get other non-worker
 * children).
 */

m_kill_procs()
{
	return(_m_kill_procs());
}
