/* L I V E   V A R I A B L E S   A N A L Y S I S */

#include <stdio.h>
#include "../share/types.h"
#include "lv.h"
#include "../share/debug.h"
#include "../share/global.h"
#include "../share/lset.h"
#include "../share/cset.h"
#include "../share/def.h"
#include "../share/files.h"
#include "../share/alloc.h"
#include "../share/map.h"
#include "../share/get.h"
#include "../share/put.h"
#include "../share/aux.h"
#include "../share/init_glob.h"
#include "../share/locals.h"
#include "../share/go.h"
#include "../../../h/em_mnem.h"
#include "../../../h/em_pseu.h"
#include "../../../h/em_spec.h"
#include "../share/parser.h"

#define newlvbx()	(bext_p) newstruct(bext_lv)
#define oldlvbx(x)	oldstruct(bext_lv,x)


/* TEMPORARY: should be put in ../../../h/em_mes.h: */
#define ms_liv 9
#define ms_ded 10

short nrglobals;
short nrvars;

STATIC int Slv;
STATIC bool mesgflag = FALSE;  /* Suppress generation of live/dead info */


STATIC clean_up()
{
	local_p *p;

	for (p = &locals[1]; p <= &locals[nrlocals]; p++) {
		oldlocal(*p);
	}
	oldmap(locals,nrlocals);
}



STATIC bool is_dir_use(l)
	line_p l;
{
	/* See if l is a direct use of some variable
	 * (i.e. not through a pointer). A LIL is a
	 * direct use of some pointer variable
	 * (and an indirect use of some other variable).
	 * A SIL is also a direct use.
	 * A LOI, however, is not an direct use of a variable.
	 * An an increment/decrement instruction is regarded
	 * as a use here, and not as a definition, as the
	 * variable is first used and than defined.
	 */

	switch(INSTR(l)) {
		case op_dee:
		case op_del:
		case op_ine:
		case op_inl:
		case op_lde:
		case op_ldl:
		case op_lil:
		case op_loe:
		case op_lol:
		case op_sil:
			return TRUE;
		default:
			return FALSE;
	}
	/* NOTREACHED */
}



STATIC bool is_indir_use(l)
	line_p l;
{
	/* See if instruction l uses some variable(s) indirectly,
	 * i.e. through a pointer or via a procedure call.
	 */

	switch(INSTR(l)) {
		case op_blm:
		case op_bls:
		case op_cai:
		case op_cal:
		case op_lar:
		case op_ldf:
		case op_lil:
		case op_lof:
		case op_loi:
		case op_los:
		case op_mon:
			return TRUE;
		default:
			return FALSE;
	}
	/* NOTREACHED */
}



STATIC bool is_def(l)
	line_p l;
{
	/* See if l does a direct definition */

	switch(INSTR(l)) {
		case op_sde:
		case op_sdl:
		case op_ste:
		case op_stl:
		case op_zre:
		case op_zrl:
			return TRUE;
		default:
			return FALSE;
	}
	/* NOTREACHED */
}


STATIC def_use(p)
	proc_p p;
{
	/* Compute DEF(b) and USE(b), for every basic block b
	 * of procedure p. DEF(b) contains the variables that
	 * are certain to be defined (assigned) in b
	 * before being used. USE(b) contains the variables
	 * that may be used in b, before being defined.
	 * (Note that uncertainty arises in the presence of
	 *  pointers and procedure calls).
	 * We compute these sets, by scanning the text of
	 * the basic block from beginning till end.
	 */

	register bblock_p b;
	register line_p l;
	short v;
	bool found;
	cset all_ind_uses;

	all_ind_uses = Cempty_set(nrvars);
	for (v = 1; v < nrlocals; v++) {
		if (!IS_REGVAR(locals[v])) {
			Cadd(LOC_TO_VARNR(v),&all_ind_uses);
		}
	}
	for (b = p->p_start; b != (bblock_p) 0; b = b->b_next) {
		USE(b) = Cempty_set(nrvars);
		DEF(b) = Cempty_set(nrvars);
		for (l = b->b_start; l != (line_p) 0; l = l->l_next) {
			if (is_def(l)) {
				/* An direct definition (i.e. not
				 * through a pointer).
				 */
				var_nr(l,&v,&found);
				if (found && !Cis_elem(v,USE(b))) {
					/* We do maintain live-dead info
					 * for this variable, and it was
					 * not used earlier in b.
					 */
					Cadd(v, &DEF(b));
				}
			} else {
				if (is_dir_use(l)) {
					var_nr(l,&v,&found);
					if (found && !Cis_elem(v,DEF(b))) {
						Cadd(v, &USE(b));
					}
				}
				if (is_indir_use(l)) {
					/* Add variable that may be used
					 * by l to USE(b).
					 */
					Cjoin(all_ind_uses,&USE(b));
				}
			}
		}
	}
	Cdeleteset(all_ind_uses);
}



STATIC unite_ins(bbset,setp)
	lset bbset;
	cset *setp;
{
	/* Take the union of L_IN(b), for all b in bbset,
	 * and put the result in setp.
	 */

	Lindex i;

	Cclear_set(setp);
	for (i = Lfirst(bbset); i != (Lindex) 0; i = Lnext(i,bbset)) {
		Cjoin(L_IN((bblock_p) Lelem(i)), setp);
	}
}



STATIC solve_lv(p)
	proc_p p;
{
	/* Solve the data flow equations for Live Variables,
	 * for procedure p. These equations are:
	 *  (1)   IN[b] = OUT[b] - DEF[b] + USE[b]
	 *  (2)   OUT(b) = IN(s1) + ... + IN(sn) ;
	 *        where SUCC(b) = {s1, ... , sn}
	 */

	register bblock_p b;
	cset newout = Cempty_set(nrvars);
	bool change = TRUE;

	for (b = p->p_start; b != (bblock_p) 0; b = b->b_next) {
		L_IN(b) = Cempty_set(nrvars);
		Ccopy_set(USE(b), &L_IN(b));
		L_OUT(b) = Cempty_set(nrvars);
	}
	while (change) {
		change = FALSE;
		for (b = p->p_start; b != (bblock_p) 0; b = b->b_next) {
			unite_ins(b->b_succ,&newout);
			if (!Cequal(newout,L_OUT(b))) {
				change = TRUE;
				Ccopy_set(newout, &L_OUT(b));
				Ccopy_set(newout, &L_IN(b));
				Csubtract(DEF(b), &L_IN(b));
				Cjoin(USE(b), &L_IN(b));
			}
		}
	}
	Cdeleteset(newout);
}


STATIC live_variables_analysis(p)
	proc_p p;
{
	make_localtab(p);
	nrvars = nrglobals + nrlocals;
	def_use(p);
	solve_lv(p);
}


STATIC init_live_dead(b)
	bblock_p b;
{
	/* For every register variable, see if it is
	 * live or dead at the end of b.
	 */

	register short v;
	local_p loc;

	for (v = 1; v <= nrlocals; v++) {
		loc = locals[v];
		if (IS_REGVAR(loc) && Cis_elem(LOC_TO_VARNR(v),L_OUT(b))) {
			LIVE(loc);
		} else {
			DEAD(loc);
		}
	}
}



STATIC line_p make_mesg(mesg,loc)
	short mesg;
	local_p loc;
{
	/* Create a line for a message stating that
	 * local variable loc is live/dead. This message
	 * looks like: "mes ms_liv,off,size" or
	 * "mes ms_ded,off,size".
	 */

	line_p l = newline(OPLIST);
	register arg_p ap;

	l->l_instr = ps_mes;
	ap = ARG(l) = newarg(ARGOFF);
	ap->a_a.a_offset = mesg;
	ap = ap->a_next = newarg(ARGOFF);
	ap->a_a.a_offset = loc->lc_off;
	ap = ap->a_next = newarg(ARGOFF);
	ap->a_a.a_offset = loc->lc_size;
	return l;
}



STATIC block_entry(b,prev)
	bblock_p b,prev;
{
	short v,vn;
	local_p loc;
	bool was_live, is_live;

	/* Generate a live/dead message for every register variable that
	 * was live at the end of prev, but dead at the beginning of b,
	 * or v.v. If prev = 0 (i.e. begin of procedure), parameters were
	 * live, normal local variables were dead.
	 */

	for (v = 1; v <= nrlocals; v++) {
		loc = locals[v];
		vn = LOC_TO_VARNR(v);
		if (prev == (bblock_p) 0) {
			was_live = loc->lc_off >= 0;
		} else {
			was_live = Cis_elem(vn,L_OUT(prev));
		}
		is_live = Cis_elem(vn,L_IN(b));
		if (was_live != is_live) {
			app_block(make_mesg((is_live?ms_liv:ms_ded),loc),b);
		}
	}
}



STATIC app_block(l,b)
	line_p l;
	bblock_p b;
{
	line_p x = b->b_start;

	if (x != (line_p) 0 && INSTR(x) == ps_pro) {
		/* start of procedure; append after pro pseudo ! */
		if ((l->l_next = x->l_next) != (line_p) 0) {
			PREV(l->l_next) = l;
		}
		x->l_next = l;
		PREV(l) = x;
	} else {
		if ((l->l_next = x) != (line_p) 0) {
			PREV(l->l_next) = l;
		}
		b->b_start = l;
		PREV(l) = (line_p) 0;
	}
}



STATIC definition(l,useless_out,v_out,mesgflag)
	line_p l;
	bool *useless_out;
	short *v_out;
	bool mesgflag;
{
	/* Process a definition. If the defined (register-) variable
	 * is live after 'l', then create a live-message and put
	 * it after 'l'.
	 */

	short v;
	bool found;
	local_p loc;

	*useless_out = FALSE;
	var_nr(l,&v,&found);
	if (found && IS_LOCAL(v)) {
		*v_out = v;
		loc = locals[TO_LOCAL(v)];
		if (IS_REGVAR(loc)) {
			if (IS_LIVE(loc)) {
				if (!mesgflag) {
					appnd_line(make_mesg(ms_liv,loc), l);
				}
				DEAD(loc);
			} else {
				*useless_out = TRUE;
			}
		}
	}
}




STATIC use(l,mesgflag)
	line_p l;
	bool mesgflag;
{
	/* Process a use. If the defined (register-) variable
	 * is dead after 'l', then create a dead-message and put
	 * it after 'l'.
	 */

	short v;
	bool found;
	local_p loc;

	var_nr(l,&v,&found);
	if (found && IS_LOCAL(v)) {
		loc = locals[TO_LOCAL(v)];
		if (IS_REGVAR(loc) && IS_DEAD(loc)) {
			if (!mesgflag) {
				appnd_line(make_mesg(ms_ded,loc), l);
			}
			LIVE(loc);
		}
	}
}



STATIC nothing() { }  /* No action to be undertaken at level 0 of parser */

STATIC rem_code(l1,l2,b)
	line_p l1,l2;
	bblock_p b;
{
	line_p l,x,y;

	x = PREV(l1);
	y = l2->l_next;
	for (l = l1; l != l2; l = l->l_next) {
		oldline(l);
	}
	if (x == (line_p) 0) {
		b->b_start = y;
	} else {
		x->l_next = y;
	}
	if (y != (line_p) 0) {
		PREV(y) = x;
	}
}




#define SIZE(v)	((offset) locals[TO_LOCAL(v)]->lc_size)




lv_mesg(p,mesgflag)
	proc_p p;
	bool mesgflag;
{
	/* Create live/dead messages for every possible register
	 * variable of p. A dead-message is put after a "use" of
	 * such a variable, if the variable becomes dead just
	 * after the use (i.e. this was its last use).
	 * A live message is put after a "definition" of such
	 * a variable, if the variable becomes live just
	 * after the definition (which will usually be the case).
	 * We traverse every basic block b of p from the last
	 * instruction of b backwards to the beginning of b.
	 * Initially, all variables that are dead at the end
	 * of b are marked dead. All others are marked live.
	 * If we come accross a definition of a variable X that
	 * was marked live, we put a live-message after the
	 * definition and mark X dead.
	 * If we come accross a use of a variable X that
	 * was marked dead, we put a dead-message after the
	 * use and mark X live.
	 * So at any point, the mark of X tells whether X is
	 * live or dead immediately before (!) that point.
	 * We also generate a message at the start of a basic block
	 * for every variable that was live at the end of the (textually)
	 * previous block, but dead at the entry of this block, or v.v.
	 * On the fly, useless assignments are removed.
	 */

	register bblock_p b;
	register line_p l;
	line_p lnp, prev;
	bblock_p prevb = (bblock_p) 0;
	short v;
	bool useless;

	for (b = p->p_start; b != (bblock_p) 0; b = b->b_next) {
		block_entry(b,prevb); /* generate message at head of block */
		prevb = b;
		if (!mesgflag) {
			init_live_dead(b);
		}
		for (l = last_instr(b); l != (line_p) 0; l = prev) {
			/* traverse backwards! */
			prev = PREV(l);
			if (is_def(l)) {
				definition(l,&useless,&v,mesgflag);
				if (useless &&   /* assignment to dead var. */
				    parse(prev,SIZE(v),&lnp,0,nothing)) {
					/* The code "VAR := expression" can
					 * be removed. 'l' is the "STL VAR",
					 * lnp is the beginning of the EM code
					 * for the expression.
					 */
					prev = PREV(lnp);
					rem_code(lnp,l,b);
OUTVERBOSE("useless assignment ,proc %d,local %d", curproc->p_id,
  (int) locals[TO_LOCAL(v)]->lc_off);
					Slv++;
				}
			} else {
				if (is_dir_use(l))  {
					use(l,mesgflag);
				}
			}
		}
	}
}


STATIC lv_extend(p)
	proc_p p;
{
	/* Allocate extended data structures for Use Definition analysis */

	register bblock_p b;

	for (b = p->p_start; b != (bblock_p) 0; b = b->b_next) {
		b->b_extend = newlvbx();
	}
}


STATIC lv_cleanup(p)
	proc_p p;
{
	/* Deallocate extended data structures for Use Definition analysis */

	register bblock_p b;

	for (b = p->p_start; b != (bblock_p) 0; b = b->b_next) {
		Cdeleteset(USE(b));
		Cdeleteset(DEF(b));
		Cdeleteset(L_IN(b));
		Cdeleteset(L_OUT(b));
		oldlvbx(b->b_extend);
	}
}

lv_flags(p)
	char *p;
{
	switch(*p) {
		case 'N':
			mesgflag = TRUE;
			break;
	}
}


lv_optimize(p)
	proc_p p;
{
	locals = (local_p *) 0;
	lv_extend(p);
	live_variables_analysis(p);
	lv_mesg(p,mesgflag);
	/* generate live-dead messages for regvars */
	lv_cleanup(p);
	clean_up();
}



main(argc,argv)
	int argc;
	char *argv[];
{
	go(argc,argv,init_globals,lv_optimize,no_action,lv_flags);
	report("useless assignments deleted",Slv);
	exit(0);
}