/*
 * Copyright (c) 1983 Regents of the University of California.
 * All rights reserved. The Berkeley Software License Agreement
 * specifies the terms and conditions for redistribution.
 */
static char rcsid[] = "$Header:optloop.c 12.0$";
#include "defs.h"
#include "optim.h"


#define SCFREE   0
#define SCSAFE   1



typedef
  struct varblock
    {
      struct varblock *next;
      field vstg;
      int memno;	/* holds memalloc for TTEMP */
      short sets;
      short uses;
      field setfirst;
    } VARBLOCK;

typedef VARBLOCK *Varp;

#define TABLESIZE 59

LOCAL Varp table[TABLESIZE];



LOCAL Varp mkbucket(vstg,memno)
field vstg;
int memno;

{
  Varp q;

  q = ALLOC(varblock);
  q->vstg = vstg;
  q->memno = memno;
  return q;
}



LOCAL Varp lookup(p)
tagptr p;

{
int vstg, memno;
int key;
Varp q, r;

switch (p->tag)
	{
	case TTEMP:
		vstg = 0;
		memno = (int) p->tempblock.memalloc;
		break;

	case TADDR:
		vstg = p->addrblock.vstg;
		memno = p->addrblock.memno;
		break;

	default:
		badtag ("lookup",p->tag);
	}
key = memno % TABLESIZE;
q = table[key];

if (q)
	{
	for (; q; r = q, q = q->next)
		if ((q->vstg == vstg) && (q->memno == memno))
			return q;
	return r->next = mkbucket(vstg,memno);
	}
else
	return table[key] = mkbucket(vstg,memno);
}



LOCAL freetable()

{
  int i;
  Varp p, q;

  for (i = 0; i < TABLESIZE; i++)
    if (table[i])
      {
	p = table[i];
	table[i] = NULL;

	while (p)
	  {
	    q = p->next;
	    free((char *) p);
	    p = q;
	  }
      }
}



Slotp newcode;
Slotp dohead, doend;
LOCAL Slotp first, last;
LOCAL commonset;
LOCAL int comocount;	/* count of number of code motions done */


optloops()

{
int	match;
Slotp	nextslot;
Slotp	sl1,sl2;
Slotp	lastlabslot;
int	lab;

if (! optimflag) return;
if (debugflag[6]) return;

lastlabslot = NULL;
comocount = 0;
for (sl1 = firstslot; sl1; sl1 = nextslot)
	{
	nextslot = sl1->next;
	switch (sl1->type)
	    {
	    case SKLABEL:
		lastlabslot = sl1;
		break;

	    case SKGOTO:
		if (lastlabslot && sl1->label == lastlabslot->label)
			{
			lab = newlabel ();
			first = optinsert (SKLABEL,0,lab,0,lastlabslot->next);
			last = sl1;
			last->label = lab;
			optloop ();
			}
		break;

	    case SKDOHEAD:
		match = 0;
		for (sl2 = sl1; sl2; sl2 = sl2->next)
			{
			if (sl2->type == SKDOHEAD) match++;
			else if (sl2->type == SKENDDO) match--;
			if (match == 0) break;
			}
		if (sl2)
			last = sl2;
		else
			fatal ("unmatched do in code buffer");
		if (sl2->type != SKENDDO)
			fatal ("internal error in optloops");

		/*  last now points to the SKENDDO slot; the SKNULL slot
		 *  is reached through last->nullslot
		 */
		last = (Slotp) last->nullslot;

		first = sl1;

		optloop ();
		break;

	    default:
		break;
	    }
	}

if (debugflag[0])
	fprintf (diagfile,"%d code motion%s performed\n",comocount,
		(comocount==1 ? "" : "s") );
return;
}



optloop()

{
newcode = NULL;

modify();

return;
}


LOCAL modify()

{
  Slotp sp;
  int s;

  scanvars();

  for (sp = first; sp != last->next; sp = sp->next)
    switch (sp->type)
      {
      case SKEQ:
	s = anex(sp->expr);
	if (s == SCSAFE)
	  removesafe (&sp->expr);
	break;

      case SKARIF:
      case SKASGOTO:
      case SKCALL:
      case SKCMGOTO:
      case SKIFN:
      case SKSTOP:
      case SKRETURN:
      case SKPAUSE:
      case SKIOIFN:
	s = anex(sp->expr);
	if (s == SCSAFE)
	  removesafe(&sp->expr);
	break;

      default:
	break;
      }

  freetable();
  return;
}


LOCAL scanvars()

{
  Slotp sp;
  Varp varinfo;
  int i;
  Varp p;

  commonset = NO;

  for (sp = first; sp != last->next; sp = sp->next)
    {
      switch (sp->type)
	{
	case SKARIF:
	case SKASGOTO:
	case SKCALL:
	case SKCMGOTO:
	case SKIFN:
	case SKSTOP:
	case SKRETURN:
	case SKPAUSE:
	case SKIOIFN:
	case SKEQ:
	  setsuses(sp->expr);
	  break;

	default:
	  break;
	}
    }

  if (commonset)
    for (i = 0; i < TABLESIZE; i++)
      for (p = table[i]; p; p = p->next)
	if (p->vstg == STGCOMMON)
	  {
	    p->sets++;
	    p->setfirst = NO;
	  }
}


LOCAL setsuses(p)
expptr p;

{
  Addrp lhs;
  Varp varinfo;
  chainp args;

  if (!p) return;

  switch (p->tag)
    {
    case TEXPR:
      switch (p->exprblock.opcode)
	{
	default:
	  setsuses(p->exprblock.leftp);
	  setsuses(p->exprblock.rightp);
	  setsuses(p->exprblock.vleng);
	  break;

	case OPASSIGN:
	  switch (p->exprblock.leftp->tag)
	    {
	    case TTEMP:
	      lhs = (Addrp) p->exprblock.leftp;
	      goto taddr;

	    case TADDR:
	      lhs = (Addrp) p->exprblock.leftp;
	      setsuses(lhs->memoffset);
	      setsuses(lhs->vleng);
	    taddr:
	      setsuses(p->exprblock.rightp);
	      setsuses(p->exprblock.vleng);
	      varinfo = lookup(lhs);
	      varinfo->sets++;
              if (varinfo->uses == 0)
	        varinfo->setfirst = YES;
	      break;

	    default:
	      fatal("O6:  l-value expected");
	    }
	  break;

	case OPSTAREQ:
	case OPPLUSEQ:
	  switch (p->exprblock.leftp->tag)
	    {
	    case TADDR:
	      lhs = (Addrp) p->exprblock.leftp;
	      break;
	    case TTEMP:
	      lhs = (Addrp) p->exprblock.leftp;
	      break;
	    default:
	      fatal("O7:  l-value expected");
	    }
	  setsuses(p->exprblock.leftp);
	  setsuses(p->exprblock.rightp);
	  setsuses(p->exprblock.vleng);
	  varinfo = lookup(lhs);
	  varinfo->sets++;
	  break;

	case OPCALL:
	  if (p->exprblock.leftp->tag != TADDR)
	    fatal("O8:  subprogram expected");
	  setsuses(p->exprblock.rightp);
	  setsuses(p->exprblock.vleng);
	  if (p->exprblock.leftp->addrblock.vstg != STGEXT) break;
	  commonset = YES;
	  if (p->exprblock.rightp == NULL) break;
	  args = p->exprblock.rightp->listblock.listp;
	  for (; args; args = args->nextp)
	    if (args->datap->tag == TADDR)
	      {
		lhs = (Addrp) args->datap;
		switch (lhs->vstg)
		  {
		  case STGARG:
		  case STGAUTO:
		  case STGBSS:
		  case STGINIT:
		  case STGCOMMON:
		  case STGEQUIV:
		  case STGREG:
		  case STGPREG:
		    varinfo = lookup(lhs);
		    varinfo->sets++;
		  }
	      }
	    else if (args->datap->tag == TTEMP)
	      {
		lhs = (Addrp) args->datap;
		varinfo = lookup (lhs);
		varinfo->sets++;
	      }
	  break;
        }

      return;

    case TTEMP:
      varinfo = lookup((Addrp) p);
      varinfo->uses++;
      return;

    case TADDR:
      setsuses(p->addrblock.memoffset);
      setsuses(p->addrblock.vleng);
      varinfo = lookup((Addrp) p);
      varinfo->uses++;
      return;

    case TLIST:
      for (args = p->listblock.listp; args; args = args->nextp)
	setsuses(args->datap);

    case TCONST:
    case TERROR:
      return;

    default:
      fatal("O9:  bad tag value");
    }
}


LOCAL int anex(p)
expptr p;

{
  int s1, s2, s3;
  expptr q;
  Varp varinfo;
  chainp ch;
  int setfirst;
  expptr expr;


  if (p == ENULL)
    return SCSAFE;

  switch (p->tag)
    {
    case TCONST:
      return SCSAFE;

    case TLIST:
      for (ch = p->listblock.listp; ch; ch = ch->nextp)
	{
	  s1 = anex (ch->datap);
	  if (s1 == SCSAFE)
	    removesafe (&ch->datap);
	}
      return SCFREE;

    case TEXPR:
      s1 = anex(p->exprblock.leftp);
      s2 = anex(p->exprblock.rightp);
      s3 = anex(p->exprblock.vleng);

      switch (p->exprblock.opcode)
	{
	case OPASSIGN:
	  expr = p->exprblock.leftp;
	  varinfo = lookup(expr);
	  setfirst = varinfo->setfirst && (varinfo->sets == 1);
	  if (expr->tag == TTEMP && setfirst &&
		s2 == SCSAFE && s3 == SCSAFE)
	    {
	      movefrtemp (expr);
	      return SCSAFE;
	    }
	  else
	    {
	      if (s2 == SCSAFE) removesafe (&p->exprblock.rightp);
	      if (s3 == SCSAFE) removesafe (&p->exprblock.vleng);
	      return SCFREE;
	    }

	case OPNEG:
	case OPNOT:
	case OPABS:
	case OPADDR:
	case OPBITNOT:
	  if ((s2 == SCSAFE) && (s3 == SCSAFE))
	    return s1;
	  else
	    return SCFREE;

	case OPCONV:
	  if ((s2 != SCSAFE) || (s3 != SCSAFE))
	    return SCFREE;

	  if (ISINT(p->exprblock.vtype))
	    return s1;
	  if (ISINT(p->exprblock.leftp->headblock.vtype))
	    return s1;

	  return SCFREE;


	case OPSTAR:
	  if (ISINT(p->exprblock.vtype))
	    goto safeop;

	  if (safefactor(p->exprblock.leftp) ||
	      safefactor(p->exprblock.rightp))
	    goto safeop;

	  goto floatop;


	case OPPLUS:
	case OPMINUS:
	  if (ISINT(p->exprblock.vtype))
	    goto safeop;

	floatop:
	  if (!(ISREAL(p->exprblock.vtype) || ISCOMPLEX(p->exprblock.vtype)))
	    return SCFREE;

	  switch (s1)
	    {
	    case SCSAFE:
	      removesafe(&p->exprblock.leftp);
	      if (s2 == SCSAFE)
		removesafe(&p->exprblock.leftp);
	      return SCFREE;

	    case SCFREE:
	      if (s2 == SCSAFE)
		removesafe(&p->exprblock.rightp);
	      return SCFREE;
	    }

	case OPOR:
	case OPAND:
	case OPEQV:
	case OPNEQV:
	case OPLT:
	case OPEQ:
	case OPGT:
	case OPLE:
	case OPNE:
	case OPGE:
	case OPLSHIFT:
	case OPMIN:
	case OPMAX:
	case OPBITOR:
	case OPBITAND:
	case OPBITXOR:
	case OPRSHIFT:
	safeop:
	  if ((p->exprblock.vleng != ENULL) && ( ! ISCONST(p->exprblock.vleng)))
	    return SCFREE;

	  switch (s1)
	    {
	    case SCSAFE:
		if (s2 == SCFREE) removesafe (&p->exprblock.leftp);
		return s2;

	    case SCFREE:
		if (s2 == SCSAFE) removesafe (&p->exprblock.rightp);
		return SCFREE;
	    }

	default:
	  if (s1 == SCSAFE) removesafe(&p->exprblock.leftp);
	  if (s2 == SCSAFE) removesafe(&p->exprblock.rightp);
	  if (s3 == SCSAFE) removesafe(&p->exprblock.vleng);
	  return SCFREE;
	}


    case TTEMP:
      varinfo = lookup(p);
      if (varinfo->sets == 0)
	return SCSAFE;
      else
	return SCFREE;

    case TADDR:
      s1 = anex(p->addrblock.memoffset);
      s2 = anex(p->addrblock.vleng);

      varinfo = lookup(p);

      if (varinfo->sets == 0)
	switch (s1)
	  {
	  case SCSAFE:
		if (s2 == SCFREE) removesafe(&p->addrblock.memoffset);
		return s2;

	  case SCFREE:
		if (s2 == SCSAFE) removesafe(&p->addrblock.vleng);
		return SCFREE;
	  }

      if (s1 == SCSAFE) removesafe(&p->addrblock.memoffset);
      if (s2 == SCSAFE) removesafe(&p->addrblock.vleng);
      return SCFREE;
    
	
    default:
      return SCFREE;
    }
}


LOCAL safefactor(p)
expptr p;

{
  if ( ! ISCONST(p))
    return NO;

  if (ISINT(p->constblock.vtype))
    if (abs(p->constblock.constant.ci) <= 1)
      return YES;

  if (ISREAL(p->constblock.vtype))
    if (abs(p->constblock.constant.cd[0]) <= 1.0)
      return YES;

  return NO;
}


LOCAL int worthcost(p)
expptr p;

{
  int cost;
  chainp q;
  expptr memoffset,vleng;

  if (p == ENULL)
    return NO;

  switch (p->tag)
    {
    case TCONST:
      return NO;

    case TTEMP:
      return NO;

    case TADDR:
      if ((memoffset = p->addrblock.memoffset) && ! ISCONST(memoffset))
	return YES;
      else if ((vleng = p->addrblock.vleng) && ! ISCONST(vleng))
	return YES;
      else
	return NO;

    case TEXPR:
      return YES;

    case TLIST:
      cost = 0;
      for (q = p->listblock.listp; q; q = q->nextp)
	{
	if (worthcost ((expptr) q->datap))
	  return YES;
	cost++;
	}
      return (cost>2 ? YES : NO);

    default:
      return NO;
    }
}


LOCAL removesafe(refexpr)
expptr *refexpr;

{
  expptr ep;
  Tempp ap;
  Slotp newslot;

  extern Addrp gettemp();

  ep = *refexpr;
  if (! worthcost(ep))
    return;

  if (ep->tag == TEXPR && ep->exprblock.opcode == OPASSIGN)
    {
      if (ep->exprblock.leftp->tag != TTEMP)
	fatal ("non-TEMP in assignment to be moved in optloop");

      newslot = optinsert (SKEQ, ep, 0, 0, first);
      *refexpr = ep->exprblock.leftp;
    }
  else
    {
      ap = (Tempp) gettemp(ep);
      newslot = optinsert (SKEQ, mkexpr(OPASSIGN,cpexpr(ap),ep), 0, 0, first);
      *refexpr = (expptr) ap;
      optinsert (SKFRTEMP,ap->memalloc,0,0,last->next);
    }

  comocount++;
  if (!newcode)
    newcode = newslot;

  return;
}


LOCAL Addrp gettemp(p)
expptr p;

{
  return mktemp(p->headblock.vtype, p->headblock.vleng);
}



LOCAL movefrtemp (expr)
Tempp	expr;

{
  Slotp	s;

  if (expr->tag != TTEMP)
    badtag ("movefrtemp",expr->tag);

  for (s = first; s; s = s->next)
    if (s->type == SKFRTEMP && s->expr == (expptr) expr->memalloc)
      {
	removeslot (s);
	insertslot (s,last->next);
	return;
      }
}
