/***** spin: pangen5.c *****/

/*
 * This file is part of the public release of Spin. It is subject to the
 * terms in the LICENSE file that is included in this source directory.
 * Tool documentation is available at http://spinroot.com
 */

#include "spin.h"
#include "y.tab.h"

typedef struct BuildStack {
	FSM_trans *t;
	struct BuildStack *nxt;
} BuildStack;

extern ProcList	*rdy;
extern int verbose, eventmapnr, claimnr, rvopt, export_ast, u_sync;
extern Element *Al_El;

static FSM_state *fsm_free;
static FSM_trans *trans_free;
static BuildStack *bs, *bf;
static int max_st_id;
static int cur_st_id;
int o_max;
FSM_state *fsm;
FSM_state **fsm_tbl;
FSM_use   *use_free;

static void ana_seq(Sequence *);
static void ana_stmnt(FSM_trans *, Lextok *, int);

extern void AST_slice(void);
extern void AST_store(ProcList *, int);
extern int  has_global(Lextok *);
extern void exit(int);

static void
fsm_table(void)
{	FSM_state *f;
	max_st_id += 2;
	/* fprintf(stderr, "omax %d, max=%d\n", o_max, max_st_id); */
	if (o_max < max_st_id)
	{	o_max = max_st_id;
		fsm_tbl = (FSM_state **) emalloc(max_st_id * sizeof(FSM_state *));
	} else
		memset((char *)fsm_tbl, 0, max_st_id * sizeof(FSM_state *));
	cur_st_id = max_st_id;
	max_st_id = 0;

	for (f = fsm; f; f = f->nxt)
		fsm_tbl[f->from] = f;
}

static int
FSM_DFS(int from, FSM_use *u)
{	FSM_state *f;
	FSM_trans *t;
	FSM_use *v;
	int n;

	if (from == 0)
		return 1;

	f = fsm_tbl[from];

	if (!f)
	{	printf("cannot find state %d\n", from);
		fatal("fsm_dfs: cannot happen\n", (char *) 0);
	}

	if (f->seen)
		return 1;
	f->seen = 1;

	for (t = f->t; t; t = t->nxt)
	{
		for (n = 0; n < 2; n++)
		for (v = t->Val[n]; v; v = v->nxt)
			if (u->var == v->var)
				return n;	/* a read or write */

		if (!FSM_DFS(t->to, u))
			return 0;
	}
	return 1;
}

static void
new_dfs(void)
{	int i;

	for (i = 0; i < cur_st_id; i++)
		if (fsm_tbl[i])
			fsm_tbl[i]->seen = 0;
}

static int
good_dead(Element *e, FSM_use *u)
{
	switch (u->special) {
	case 2:	/* ok if it's a receive */
		if (e->n->ntyp == ASGN
		&&  e->n->rgt->ntyp == CONST
		&&  e->n->rgt->val == 0)
			return 0;
		break;
	case 1:	/* must be able to use oval */
		if (e->n->ntyp != 'c'
		&&  e->n->ntyp != 'r')
			return 0;	/* can't really happen */
		break;
	}
	return 1;
}

#if 0
static int howdeep = 0;
#endif

static int
eligible(FSM_trans *v)
{	Element	*el = ZE;
	Lextok	*lt = ZN;

	if (v) el = v->step;
	if (el) lt = v->step->n;

	if (!lt				/* dead end */
	||  v->nxt			/* has alternatives */
	||  el->esc			/* has an escape */
	||  (el->status&CHECK2)		/* remotely referenced */
	||  lt->ntyp == ATOMIC
	||  lt->ntyp == NON_ATOMIC	/* used for inlines -- should be able to handle this */
	||  lt->ntyp == IF
	||  lt->ntyp == C_CODE
	||  lt->ntyp == C_EXPR
	||  has_lab(el, 0)		/* any label at all */
	||  lt->ntyp == SET_P		/* to prevent multiple set_p merges */

	||  lt->ntyp == DO
	||  lt->ntyp == UNLESS
	||  lt->ntyp == D_STEP
	||  lt->ntyp == ELSE
	||  lt->ntyp == '@'
	||  lt->ntyp == 'c'
	||  lt->ntyp == 'r'
	||  lt->ntyp == 's')
		return 0;

	if (!(el->status&(2|4)))	/* not atomic */
	{	int unsafe = (el->status&I_GLOB)?1:has_global(el->n);
		if (unsafe)
			return 0;
	}

	return 1;
}

static int
canfill_in(FSM_trans *v)
{	Element	*el = v->step;
	Lextok	*lt = v->step->n;

	if (!lt				/* dead end */
	||  v->nxt			/* has alternatives */
	||  el->esc			/* has an escape */
	||  (el->status&CHECK2))	/* remotely referenced */
		return 0;

	if (!(el->status&(2|4))		/* not atomic */
	&&  ((el->status&I_GLOB)
	||   has_global(el->n)))	/* and not safe */
		return 0;

	return 1;
}

static int
pushbuild(FSM_trans *v)
{	BuildStack *b;

	for (b = bs; b; b = b->nxt)
		if (b->t == v)
			return 0;
	if (bf)
	{	b = bf;
		bf = bf->nxt;
	} else
		b = (BuildStack *) emalloc(sizeof(BuildStack));
	b->t = v;
	b->nxt = bs;
	bs = b;
	return 1;
}

static void
popbuild(void)
{	BuildStack *f;
	if (!bs)
		fatal("cannot happen, popbuild", (char *) 0);
	f = bs;
	bs = bs->nxt;
	f->nxt = bf;
	bf = f;				/* freelist */
}

static int
build_step(FSM_trans *v)
{	FSM_state *f;
	Element	*el;
#if 0
	Lextok	*lt = ZN;
#endif
	int	st;
	int	r;

	if (!v) return -1;

	el = v->step;
	st = v->to;

	if (!el) return -1;

	if (v->step->merge)
		return v->step->merge;	/* already done */

	if (!eligible(v))		/* non-blocking */
		return -1;

	if (!pushbuild(v))		/* cycle detected */
		return -1;		/* break cycle */

	f = fsm_tbl[st];
#if 0
	lt = v->step->n;
	if (verbose&32)
	{	if (++howdeep == 1)
			printf("spin: %s:%d, merge:\n", lt->fn->name, lt->ln);
		printf("\t[%d] <seqno %d>\t", howdeep, el->seqno);
		comment(stdout, lt, 0);
		printf(";\n");
	}
#endif
	r = build_step(f->t);
	v->step->merge = (r == -1) ? st : r;
#if 0
	if (verbose&32)
	{	printf("	merge value: %d (st=%d,r=%d, line %d)\n",
			v->step->merge, st, r, el->n->ln);
		howdeep--;
	}
#endif
	popbuild();

	return v->step->merge;
}

static void
FSM_MERGER(/* char *pname */ void)	/* find candidates for safely merging steps */
{	FSM_state *f, *g;
	FSM_trans *t;
	Lextok	*lt;

	for (f = fsm; f; f = f->nxt)		/* all states */
	for (t = f->t; t; t = t->nxt)		/* all edges */
	{	if (!t->step) continue;		/* happens with 'unless' */

		t->step->merge_in = f->in;	/* ?? */

		if (t->step->merge)
			continue;
		lt = t->step->n;

		if (lt->ntyp == 'c'
		||  lt->ntyp == 'r'
		||  lt->ntyp == 's')	/* blocking stmnts */
			continue;	/* handled in 2nd scan */

		if (!eligible(t))
			continue;

		g = fsm_tbl[t->to];
		if (!g || !eligible(g->t))
		{
#define SINGLES
#ifdef SINGLES
			t->step->merge_single = t->to;
#if 0
			if ((verbose&32))
			{	printf("spin: %s:%d, merge_single:\n\t<seqno %d>\t",
					t->step->n->fn->name,
					t->step->n->ln,
					t->step->seqno);
				comment(stdout, t->step->n, 0);
				printf(";\n");
			}
#endif
#endif
			/* t is an isolated eligible step:
			 *
			 * a merge_start can connect to a proper
			 * merge chain or to a merge_single
			 * a merge chain can be preceded by
			 * a merge_start, but not by a merge_single
			 */

			continue;
		}

		(void) build_step(t);
	}

	/* 2nd scan -- find possible merge_starts */

	for (f = fsm; f; f = f->nxt)		/* all states */
	for (t = f->t; t; t = t->nxt)		/* all edges */
	{	if (!t->step || t->step->merge)
			continue;

		lt = t->step->n;
#if 0
	4.1.3:
	an rv send operation ('s') inside an atomic, *loses* atomicity
	when executed, and should therefore never be merged with a subsequent
	statement within the atomic sequence
	the same is not true for non-rv send operations;
	6.2.2:
	RUN statements can start a new process at a higher priority level
	which interferes with statement merging, so it too is not a suitable
	merge target
#endif

		if ((lt->ntyp == 'c' && !any_oper(lt->lft, RUN)) /* 2nd clause 6.2.2 */
		||  lt->ntyp == 'r'
		||  (lt->ntyp == 's' && u_sync == 0))	/* added !u_sync in 4.1.3 */
		{	if (!canfill_in(t))		/* atomic, non-global, etc. */
				continue;

			g = fsm_tbl[t->to];
			if (!g || !g->t || !g->t->step)
				continue;
			if (g->t->step->merge)
				t->step->merge_start = g->t->step->merge;
#ifdef SINGLES
			else if (g->t->step->merge_single)
				t->step->merge_start = g->t->step->merge_single;
#endif
#if 0
			if ((verbose&32)
			&& t->step->merge_start)
			{	printf("spin: %s:%d, merge_START:\n\t<seqno %d>\t",
						lt->fn->name, lt->ln,
						t->step->seqno);
				comment(stdout, lt, 0);
				printf(";\n");
			}
#endif
		}
	}
}

static void
FSM_ANA(void)
{	FSM_state *f;
	FSM_trans *t;
	FSM_use *u, *v, *w;
	int n;

	for (f = fsm; f; f = f->nxt)		/* all states */
	for (t = f->t; t; t = t->nxt)		/* all edges */
	for (n = 0; n < 2; n++)			/* reads and writes */
	for (u = t->Val[n]; u; u = u->nxt)
	{	if (!u->var->context	/* global */
		||   u->var->type == CHAN
		||   u->var->type == STRUCT)
			continue;
		new_dfs();
		if (FSM_DFS(t->to, u))	/* cannot hit read before hitting write */
			u->special = n+1;	/* means, reset to 0 after use */
	}

	if (!export_ast)
	for (f = fsm; f; f = f->nxt)
	for (t = f->t; t; t = t->nxt)
	for (n = 0; n < 2; n++)
	for (u = t->Val[n], w = (FSM_use *) 0; u; )
	{	if (u->special)
		{	v = u->nxt;
			if (!w)			/* remove from list */
				t->Val[n] = v;
			else
				w->nxt = v;
#if q
			if (verbose&32)
			{	printf("%s : %3d:  %d -> %d \t",
					t->step->n->fn->name,
					t->step->n->ln,
					f->from,
					t->to);
				comment(stdout, t->step->n, 0);
				printf("\t%c%d: %s\n", n==0?'R':'L',
					u->special, u->var->name);
			}
#endif
			if (good_dead(t->step, u))
			{	u->nxt = t->step->dead;	/* insert into dead */
				t->step->dead = u;
			}
			u = v;
		} else
		{	w = u;
			u = u->nxt;
	}	}
}

void
rel_use(FSM_use *u)
{
	if (!u) return;
	rel_use(u->nxt);
	u->var = (Symbol *) 0;
	u->special = 0;
	u->nxt = use_free;
	use_free = u;
}

static void
rel_trans(FSM_trans *t)
{
	if (!t) return;
	rel_trans(t->nxt);
	rel_use(t->Val[0]);
	rel_use(t->Val[1]);
	t->Val[0] = t->Val[1] = (FSM_use *) 0;
	t->nxt = trans_free;
	trans_free = t;
}

static void
rel_state(FSM_state *f)
{
	if (!f) return;
	rel_state(f->nxt);
	rel_trans(f->t);
	f->t = (FSM_trans *) 0;
	f->nxt = fsm_free;
	fsm_free = f;
}

static void
FSM_DEL(void)
{
	rel_state(fsm);
	fsm = (FSM_state *) 0;
}

static FSM_state *
mkstate(int s)
{	FSM_state *f;

	/* fsm_tbl isn't allocated yet */
	for (f = fsm; f; f = f->nxt)
		if (f->from == s)
			break;
	if (!f)
	{	if (fsm_free)
		{	f = fsm_free;
			memset(f, 0, sizeof(FSM_state));
			fsm_free = fsm_free->nxt;
		} else
			f = (FSM_state *) emalloc(sizeof(FSM_state));
		f->from = s;
		f->t = (FSM_trans *) 0;
		f->nxt = fsm;
		fsm = f;
		if (s > max_st_id)
			max_st_id = s;
	}
	return f;
}

static FSM_trans *
get_trans(int to)
{	FSM_trans *t;

	if (trans_free)
	{	t = trans_free;
		memset(t, 0, sizeof(FSM_trans));
		trans_free = trans_free->nxt;
	} else
		t = (FSM_trans *) emalloc(sizeof(FSM_trans));

	t->to = to;
	return t;
}

static void
FSM_EDGE(int from, int to, Element *e)
{	FSM_state *f;
	FSM_trans *t;

	f = mkstate(from);	/* find it or else make it */
	t = get_trans(to);

	t->step = e;
	t->nxt = f->t;
	f->t = t;

	f = mkstate(to);
	f->in++;

	if (export_ast)
	{	t = get_trans(from);
		t->step = e;
		t->nxt = f->p;	/* from is a predecessor of to */
		f->p = t;
	}

	if (t->step)
	{	ana_stmnt(t, t->step->n, 0);
	}
}

#define LVAL	1
#define RVAL	0

static void
ana_var(FSM_trans *t, Lextok *now, int usage)
{	FSM_use *u, *v;

	if (!t || !now || !now->sym)
		return;

	if (now->sym->name[0] == '_'
	&&  (strcmp(now->sym->name, "_") == 0
	||   strcmp(now->sym->name, "_pid") == 0
	||   strcmp(now->sym->name, "_priority") == 0
	||   strcmp(now->sym->name, "_last") == 0))
		return;

	v = t->Val[usage];
	for (u = v; u; u = u->nxt)
		if (u->var == now->sym)
			return;	/* it's already there */

	if (!now->lft)
	{	/* not for array vars -- it's hard to tell statically
		   if the index would, at runtime, evaluate to the
		   same values at lval and rval references
		*/
		if (use_free)
		{	u = use_free;
			use_free = use_free->nxt;
		} else
			u = (FSM_use *) emalloc(sizeof(FSM_use));
	
		u->var = now->sym;
		u->nxt = t->Val[usage];
		t->Val[usage] = u;
	} else
		 ana_stmnt(t, now->lft, RVAL);	/* index */

	if (now->sym->type == STRUCT
	&&  now->rgt
	&&  now->rgt->lft)
		ana_var(t, now->rgt->lft, usage);
}

static void
ana_stmnt(FSM_trans *t, Lextok *now, int usage)
{	Lextok *v;

	if (!t || !now) return;

	switch (now->ntyp) {
	case '.':
	case BREAK:
	case GOTO:
	case CONST:
	case TIMEOUT:
	case NONPROGRESS:
	case  ELSE:
	case '@':
	case 'q':
	case IF:
	case DO:
	case ATOMIC:
	case NON_ATOMIC:
	case D_STEP:
	case C_CODE:
	case C_EXPR:
		break;

	case ',': /* reached with SET_P and array initializers */
		if (now->lft && now->lft->rgt)
		{	ana_stmnt(t, now->lft->rgt, RVAL);
		}
		break;

	case '!':	
	case UMIN:
	case '~':
	case ENABLED:
	case GET_P:
	case PC_VAL:
	case LEN:
	case FULL:
	case EMPTY:
	case NFULL:
	case NEMPTY:
	case ASSERT:
	case 'c':
		ana_stmnt(t, now->lft, RVAL);
		break;

	case SET_P:
		ana_stmnt(t, now->lft, RVAL); /* ',' */
		ana_stmnt(t, now->lft->rgt, RVAL);
		break;

	case '/':
	case '*':
	case '-':
	case '+':
	case '%':
	case '&':
	case '^':
	case '|':
	case LT:
	case GT:
	case LE:
	case GE:
	case NE:
	case EQ:
	case OR:
	case AND:
	case LSHIFT:
	case RSHIFT:
		ana_stmnt(t, now->lft, RVAL);
		ana_stmnt(t, now->rgt, RVAL);
		break;

	case ASGN:
		if (check_track(now) == STRUCT) { break; }

		ana_stmnt(t, now->lft, LVAL);
		if (now->rgt->ntyp)
			ana_stmnt(t, now->rgt, RVAL);
		break;

	case PRINT:
	case RUN:
		for (v = now->lft; v; v = v->rgt)
			ana_stmnt(t, v->lft, RVAL);
		break;

	case PRINTM:
		if (now->lft && !now->lft->ismtyp)
			ana_stmnt(t, now->lft, RVAL);
		break;

	case 's':
		ana_stmnt(t, now->lft, RVAL);
		for (v = now->rgt; v; v = v->rgt)
			ana_stmnt(t, v->lft, RVAL);
		break;

	case 'R':
	case 'r':
		ana_stmnt(t, now->lft, RVAL);
		for (v = now->rgt; v; v = v->rgt)
		{	if (v->lft->ntyp == EVAL)
				ana_stmnt(t, v->lft->lft, RVAL);
			else
			if (v->lft->ntyp != CONST
			&&  now->ntyp != 'R')		/* was v->lft->ntyp */
				ana_stmnt(t, v->lft, LVAL);
		}
		break;

	case '?':
		ana_stmnt(t, now->lft, RVAL);
		if (now->rgt)
		{	ana_stmnt(t, now->rgt->lft, RVAL);
			ana_stmnt(t, now->rgt->rgt, RVAL);
		}
		break;

	case NAME:
		ana_var(t, now, usage);
		break;

	case   'p':	/* remote ref */
		ana_stmnt(t, now->lft->lft, RVAL);	/* process id */
		ana_var(t, now, RVAL);
		ana_var(t, now->rgt, RVAL);
		break;

	default:
		if (0) printf("spin: %s:%d, bad node type %d usage %d (ana_stmnt)\n",
			now->fn->name, now->ln, now->ntyp, usage);
		fatal("aborting (ana_stmnt)", (char *) 0);
	}
}

void
ana_src(int dataflow, int merger)	/* called from main.c and guided.c */
{	ProcList *p;
	Element *e;
#if 0
	int counter = 1;
#endif
	for (p = rdy; p; p = p->nxt)
	{
		ana_seq(p->s);
		fsm_table();

		e = p->s->frst;
#if 0
		if (dataflow || merger)
		{	printf("spin: %d, optimizing '%s'",
				counter++, p->n->name);
			fflush(stdout);
		}
#endif
		if (dataflow)
		{	FSM_ANA();
		}
		if (merger)
		{	FSM_MERGER(/* p->n->name */);
			huntele(e, e->status, -1)->merge_in = 1; /* start-state */
#if 0
			printf("\n");
#endif
		}
		if (export_ast)
			AST_store(p, huntele(e, e->status, -1)->seqno);

		FSM_DEL();
	}
	for (e = Al_El; e; e = e->Nxt)
	{
		if (!(e->status&DONE) && (verbose&32))
		{	printf("unreachable code: ");
			printf("%s:%3d  ", e->n->fn->name, e->n->ln);
			comment(stdout, e->n, 0);
			printf("\n");
		}
		e->status &= ~DONE;
	}
	if (export_ast)
	{	AST_slice();
		alldone(0);	/* changed in 5.3.0: was exit(0) */
	}
}

void
spit_recvs(FILE *f1, FILE *f2)	/* called from pangen2.c */
{	Element *e;
	Sequence *s;
	extern int Unique;

	fprintf(f1, "unsigned char Is_Recv[%d];\n", Unique);

	fprintf(f2, "void\nset_recvs(void)\n{\n");
	for (e = Al_El; e; e = e->Nxt)
	{	if (!e->n) continue;

		switch (e->n->ntyp) {
		case 'r':
markit:			fprintf(f2, "\tIs_Recv[%d] = 1;\n", e->Seqno);
			break;
		case D_STEP:
			s = e->n->sl->this;
			switch (s->frst->n->ntyp) {
			case DO:
				fatal("unexpected: do at start of d_step", (char *) 0);
			case IF: /* conservative: fall through */
			case 'r': goto markit;
			}
			break;
		}
	}
	fprintf(f2, "}\n");

	if (rvopt)
	{
	fprintf(f2, "int\nno_recvs(int me)\n{\n");
	fprintf(f2, "	int h; uchar ot; short tt;\n");
	fprintf(f2, "	Trans *t;\n");
	fprintf(f2, "	for (h = BASE; h < (int) now._nr_pr; h++)\n");
	fprintf(f2, "	{	if (h == me) continue;\n");
	fprintf(f2, "		tt = (short) ((P0 *)pptr(h))->_p;\n");
	fprintf(f2, "		ot = (uchar) ((P0 *)pptr(h))->_t;\n");
	fprintf(f2, "		for (t = trans[ot][tt]; t; t = t->nxt)\n");
	fprintf(f2, "			if (Is_Recv[t->t_id]) return 0;\n");
	fprintf(f2, "	}\n");
	fprintf(f2, "	return 1;\n");
	fprintf(f2, "}\n");
	}
}

static void
ana_seq(Sequence *s)
{	SeqList *h;
	Sequence *t;
	Element *e, *g;
	int From, To;

	for (e = s->frst; e; e = e->nxt)
	{	if (e->status & DONE)
			goto checklast;

		e->status |= DONE;

		From = e->seqno;

		if (e->n->ntyp == UNLESS)
			ana_seq(e->sub->this);
		else if (e->sub)
		{	for (h = e->sub; h; h = h->nxt)
			{	g = huntstart(h->this->frst);
				To = g->seqno;

				if (g->n->ntyp != 'c'
				||  g->n->lft->ntyp != CONST
				||  g->n->lft->val != 0
				||  g->esc)
					FSM_EDGE(From, To, e);
				/* else it's a dead link */
			}
			for (h = e->sub; h; h = h->nxt)
				ana_seq(h->this);
		} else if (e->n->ntyp == ATOMIC
			||  e->n->ntyp == D_STEP
			||  e->n->ntyp == NON_ATOMIC)
		{
			t = e->n->sl->this;
			g = huntstart(t->frst);
			t->last->nxt = e->nxt;
			To = g->seqno;
			FSM_EDGE(From, To, e);

			ana_seq(t);
		} else 
		{	if (e->n->ntyp == GOTO)
			{	g = get_lab(e->n, 1);
				g = huntele(g, e->status, -1);
				if (!g)
				{	fatal("unexpected error 2", (char *) 0);
				}
				To = g->seqno;
			} else if (e->nxt)
			{	g = huntele(e->nxt, e->status, -1);
				if (!g)
				{	fatal("unexpected error 3", (char *) 0);
				}
				To = g->seqno;
			} else
				To = 0;

			FSM_EDGE(From, To, e);

			if (e->esc
			&&  e->n->ntyp != GOTO
			&&  e->n->ntyp != '.')
			for (h = e->esc; h; h = h->nxt)
			{	g = huntstart(h->this->frst);
				To = g->seqno;
				FSM_EDGE(From, To, ZE);
				ana_seq(h->this);
			}
		}

checklast:	if (e == s->last)
			break;
	}
}
