/* $Id$ */
/*
 * (c) copyright 1987 by the Vrije Universiteit, Amsterdam, The Netherlands.
 * See the copyright notice in the ACK home directory, in the file "Copyright".
 */

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

#include <stdlib.h>
#include <stdio.h>
#include <em_mnem.h>
#include <em_pseu.h>
#include <em_spec.h>
#include <em_mes.h>
#include <em_ego.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/utils.h"
#include "../share/init_glob.h"
#include "../share/locals.h"
#include "../share/go.h"
#include "../share/parser.h"

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


short nrvars;

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

STATIC void app_block();

STATIC void clean_up()
{
	local_p *p;

	for (p = &locals[1]; p <= &locals[nrlocals]; p++) {
		oldlocal(*p);
	}
	oldmap((void **) 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 void 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 void 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 void 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 void live_variables_analysis(p)
	proc_p p;
{
	make_localtab(p);
	nrvars = nrglobals + nrlocals;
	def_use(p);
	solve_lv(p);
}


STATIC void 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_ego,ego_live,off,size" or
	 * "mes ms_ego,ego_dead,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 = ms_ego;
	ap = ap->a_next = 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 void 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];
		if (IS_REGVAR(loc)) {
			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?ego_live:ego_dead),loc),b);
			}
		}
	}
}



STATIC void 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 void 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)) {
			/*	Tricky stuff here. Make sure that a variable
				that is assigned to is alive, at least for
				a very very short time. Otherwize, the
				register allocation pass might think that it
				is never alive, and (incorrectly) use the
				same register for this variable as for 
				another variable, that is alive at this point.
				If this variable is dead after the assignment,
				the two messages (ego_live, ego_dead) are right
				after each other. Luckily, this IS an interval.
			*/
			if (!mesgflag) {
				appnd_line(make_mesg(ego_live,loc), l);
				l = l->l_next;
			}
			if (IS_LIVE(loc)) {
				DEAD(loc);
			} else {
				if (!mesgflag) {
					appnd_line(make_mesg(ego_dead, loc), l);
				}
				*useless_out = TRUE;
			}
		}
	}
}




STATIC void 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(ego_dead,loc), l);
			}
			LIVE(loc);
		}
	}
}



/* ARGSUSED */
STATIC void nothing(line_p l1, line_p l2, offset size)
{ }  /* No action to be undertaken at level 0 of parser */

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

	x = PREV(l1);
	y = l2->l_next;
	for (l = l1; l != l2; l = next) {
		next = 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)




STATIC void lv_mesg(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 {
				}
			} else {
				if (is_dir_use(l))  {
					use(l,mesgflag);
				}
			}
		}
	}
}


STATIC void lv_extend(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 void lv_cleanup(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);
	}
}

void lv_flags(void *vp)
{
	char *p = vp;

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


void lv_optimize(void *vp)
{
	proc_p p = vp;

	if (IS_ENTERED_WITH_GTO(p)) return;
	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();
}



int 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);
}
