/************************************************************************/
/*                                                                      */
/* Copyright (c) 1988 -                                                 */
/*     The Rector and Visitors of the University of Virginia            */
/*                                                                      */
/* This code may not be distributed further without permission          */
/*      from the University of Virginia.                                */
/* This code is distributed WITHOUT ANY WARRANTY.  No claims are        */
/*      made as to whether it serves any particular purpose or even     */
/*      works at all.                                                   */
/*                                                                      */
/************************************************************************/

#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include "size.h"
#include "types.h"
#include "tword.h"
#include "scan.h"
#include "node.h"
#include "peep.h"
#include "error.h"

#define  DERIVED TW_MASK
#define  MAXQUALNAME 256
#define  NUMGREG 9
#define  NUMFREG 10
#define  MAXSTACK 50

int fnum[OB_MAXLEVEL];  /* used to define function specific symbols */

int mylabel = 0;
int stmtno = 0;         /*  Statement Number  */
int fstmtno;            /*  Statement Number at top of current func */
int segment = 99;       /*  Current segment  -  ridiculous value  */
int currlevel = 0;
int spacereq[OB_MAXLEVEL];
int argreq = 0;
int slabels = 0;
int defaultlab;

int regbusy[NUMGREG];
int fregbusy[NUMFREG];
int rstack[MAXSTACK];
int fstack[MAXSTACK];
int rstk = -1;
int fstk = -1;
int gpregused;
int fpregused;

char *fnametab[OB_MAXLEVEL];
TWORD functype[OB_MAXLEVEL];

extern linelist *t;
extern int opeep, opt_g, opt_p, opt_t;

extern void as_emit0(), as_emit1(), as_emit2();

/*
 * Note that when an instruction is ouput using as_emit2(), the
 * peephole buffer is flushed and the instruction is output without
 * optimization.  Whenever peephole optimization might generate
 * incorrect code, as_emit2() must be used.
 * The patterns in the peephole optimizer all operate under this
 * assumption:  when a value v is stored in a register %rn, %rn will
 * be referenced exactly once to retrieve v.  If we ever generate
 * code that stores v in %rn and references v via %rn more than once,
 * as_emit2() must output the instructions.
 * 
 * Example:  The Pascal sqr() function is implemented by multiplying
 * the register that holds the value to be squared by the register
 * itself.  For example: "    mulw2   %r1,%r1".  This instruction uses
 * the value in %r1 twice, so we must use as_emit2().
 *
 * Note also that as_emit2() is often used to efficiently output
 * instructions that have no chance of optimization.
 */

/*
 * terror - print out type error messages
 */

terror(where, typeid)
	char *where;
	TWORD typeid;
{
	sprintf (err_text, "cgen: illegal type (%u) %s", typeid, where);
	err_msg(E_INTERNAL, E_CURLINE);	/* terminates compilation */
}

operror(s, opcode)
	char *s;
	int opcode;
{
	sprintf (err_text, s, opcode);
	err_msg(E_INTERNAL, E_CURLINE);
}

/*
 * tosgp - return top of general purpose register stack
 */

#define tosgp() (rstack[rstk])

/*
 * tosfp - return top of floating point register stack
 */

#define tosfp() (fstack[fstk])

/*
 * nextgp - returns next free general purpose register and determines the
 * order of register usage.  Note that %r0 %r1 %r2 are scratch registers
 * that are not saved by the callee.  The register "save" instruction
 * takes an integer argument from 0 to 6 that is the number of registers
 * to save, starting from %r8 down to %r3.  Function return values are
 * placed in %r0 (and %r1 if type "double").
 */

int
nextgp()
{	

	/* register usage order */
	static int reg[NUMGREG] = {0, 1, 2, 8, 7, 6, 5, 4, 3};
	register int i;

	for (i = 0; i < NUMGREG && regbusy[reg[i]]; i++)
		;
	if (i >= NUMGREG)
		operror ("ran out of registers (expression too complex)", 0);
	return(reg[i]);
}
	

/*
 * pushgp - push a register on the general-purpose register stack
 */
int
pushgp()
{
	register int reg;
	reg = nextgp();
	regbusy[reg] = 1;
	rstack[++rstk] = reg;
	return (reg);
}

/*
 * pushfp - push a register on the floating-point register stack
 */
int
pushfp()
{
	register int i;

	for (i = 0; i <= NUMFREG-1; i++)
		if (!fregbusy[i]) {
			fregbusy[i] = 1;
			fstack[++fstk] = i;
			return (i);
		}
	operror("ran out of floating-point registers (expression too complex)", 0);
	return(0);
}

/*
 * popgp - pop a single register off the general-purpose register stack
 */
int
popgp()
{
	register int reg;
	regbusy[ reg = rstack[rstk--] ] = 0;
	return(reg);
}

/*
 * popfp - pop a single register off the floating-point register stack
 */
int
popfp()
{
	register int reg;
	fregbusy[ reg = fstack[fstk--] ] = 0;
	return(reg);
}


/*
 *  freegp - moves the contents of a gp register to a free register.
 *  Marks freed register as busy so that it doesn't get reallocated.
 */

freegp(reg, emit)
	int reg;
	void (*emit)();
{
	register int i, newreg;

	if (regbusy[reg]) {
		newreg = nextgp();
		regbusy[newreg] = 1;  
		for (i=0; i <= rstk; i++) {
			if (reg == rstack[i])
				rstack[i] = newreg;
		}
		sprintf (t->line, "\tmovw\t%%r%d,%%r%d", reg, newreg);
		(*emit)();
	}
	else
		regbusy[reg] = 1;
}


/*
 * jmpinstr(opcode) returns the jump instruction for opcode.
 */

char *
jmpinstr(opcode)
	int opcode;
{
	switch(opcode) {

	case SY_EQUAL: case ND_JMPEQ:
		return("je");

	case SY_NE: case ND_JMPNE:
		return("jne");

	case SY_LT: case ND_JMPLT:
		return("jl");

	case SY_LE: case ND_JMPLE:
		return("jle");

	case SY_GT: case ND_JMPGT:
		return("jg");

	case SY_GE: case ND_JMPGE:
		return("jge");

	default:
		operror ("cgen: illegal operator (%d) in jmpinstr()", opcode);
	}
	return("ERROR");
}

/*
 * cmpinstr(typeid) returns the comparison instruction for type "typeid"
 */

char *
cmpinstr(typeid)
	TWORD typeid;
{
	switch (typeid) {

	case TW_CHAR:
		return("cmpb");

	case TW_INT:
		return("cmpw");

	case TW_FLOAT:
		return("mfcmps");

	case TW_DOUBLE:
		return("mfcmpd");

	default:
		if ((typeid & DERIVED) != 0)
			return("cmpw");
		else
			terror ("in cmpinstr()", typeid);
		break;
	}
	return("ERROR");
}

/*
 * movinstr(typeid) returns the move instruction for type "typeid"
 */

char *
movinstr(typeid)
	TWORD typeid;
{
	switch (typeid) {

	case TW_CHAR:
		return("movb");   /* no sign bit extension */

	case TW_INT:
		return("movw");

	case TW_FLOAT:
		return("mmovss");

	case TW_DOUBLE:
		return("mmovdd");

	default:
		if ((typeid & DERIVED) != 0)
			return("movw");
		else
			terror ("in movinstr()", typeid);
		break;  
	}
	return("ERROR");
}

/*
 * fullname() fully qualifies a function name by prepending
 * (with '_' separation) all enclosing function/procedure names
 * appends a '_' to names at level 1 (avoid conflicts with
 * system routine names).
 */

char *
fullname(id, blev)
	register char *id;
	register int blev;
{
	static char fnbuf[MAXQUALNAME];
	register int i;
	register char *p, *q;

	if (blev == 0)
		return(id);
	for (i = 1, p = fnbuf; i < blev; ++i) {
		q = fnametab[i];
		while (*p++ = *q++)
			;
		*(p-1) = '_';
	}
	while (*p++ = *id++)
		;
	if (blev == 1) {
		*(p-1) = '_';
		*p = '\0';
	}
	if (p >= fnbuf + MAXQUALNAME)
		operror("cgen:  qualified function name too long", 0);
	return(fnbuf);
}


c_address(typeid, id, class, blev)
	TWORD typeid;
	int class, blev;
	char *id;
{
	static char Dfmt[] = "\tmovw\t_DISPLAY+%d,%%r%d";
	register int reg;
 
	if (*id == '$')   /* compiler-generated temporary variable */
		*id = '.';    /* assembler prefers '.' */

	reg = pushgp();
	if (TW_ISFTN(typeid)) {  /* function name -- qualify it */
		sprintf (t->line, "\tmovaw\t%s,%%r%d", fullname(id, blev), reg);
		as_emit1();
		return;
	}
	switch (class) {

	case SC_EXTERN: case SC_EXTDEF:
		sprintf (t->line, "\tmovaw\t%s,%%r%d", id, reg);
		as_emit1();
		break;

	case SC_PARAM: 
		if (blev == currlevel) {   /*  Local parm  */
			sprintf(t->line, "\tmovaw\t%s.%d(%%ap),%%r%d",
				id, blev, reg);
			as_emit1();
		}
		else {  /* up level reference to parameter. */
			sprintf (t->line, Dfmt, blev * 8, reg);
			as_emit1();
			sprintf (t->line, "\tmovaw\t%s.%d(%%r%d),%%r%d",
				id, blev, reg, reg);
			as_emit1();
		}
		break;
  
	case SC_AUTO:
		if (blev == 1 && *id != '.') {  /* Global variable  */
			sprintf (t->line, "\tmovaw\t%s,%%r%d", id, reg);
			as_emit1();
		}
		else if (blev == currlevel) {   /* Local variable */
			sprintf(t->line, "\tmovaw\t%s.%d(%%fp),%%r%d",
				id, blev, reg);
			as_emit1();
		}
		else {  /* up level reference to variable */
			sprintf (t->line, Dfmt, blev * 8 + 4, reg);
			as_emit1();
			sprintf (t->line, "\tmovaw\t%s.%d(%%r%d),%%r%d",
				id, blev, reg, reg);
			as_emit1();
		}
		break;
	}
}

c_aoper(opcode, typeid)
	int opcode;
	TWORD typeid;
{
	register int reg;
	register char tchar;

	switch (opcode) {

	case SY_PLUS:
		switch (typeid) {

		case TW_CHAR: 
			reg = popgp();
			sprintf (t->line, "\taddb2\t%%r%d,%%r%d", reg, tosgp());
			break;

		case TW_INT: 
			reg = popgp();
			sprintf (t->line, "\taddw2\t%%r%d,%%r%d", reg, tosgp());
			break;

		case TW_FLOAT:
			reg = popfp();
			sprintf (t->line, "\tmfadds2\t%%f%d,%%f%d", reg, tosfp());
			break;

		case TW_DOUBLE:
			reg = popfp();
			sprintf (t->line, "\tmfaddd2\t%%f%d,%%f%d", reg, tosfp());
			break;

		default:
			if ((typeid & DERIVED) != 0) {
				reg = popgp();
				sprintf (t->line, "\taddw2\t%%r%d,%%r%d", reg, tosgp());
			}
			else
				terror ("for SY_PLUS", typeid);
			break;
		}
		as_emit1();
		break;

	case SY_MINUS:
		switch (typeid) {

		case TW_CHAR: 
			reg = popgp();
			sprintf (t->line, "\tsubb2\t%%r%d,%%r%d", reg, tosgp());
			break;

		case TW_INT: 
			reg = popgp();
			sprintf (t->line, "\tsubw2\t%%r%d,%%r%d", reg, tosgp());
			break;

		case TW_FLOAT:
			reg = popfp();
			sprintf (t->line, "\tmfsubs2\t%%f%d,%%f%d", reg, tosfp());
			break;

		case TW_DOUBLE:
			reg = popfp();
			sprintf (t->line, "\tmfsubd2\t%%f%d,%%f%d", reg, tosfp());
			break;

		default:
			if ((typeid & DERIVED) != 0) {
				reg = popgp();
				sprintf (t->line, "\tsubw2\t%%r%d,%%r%d", reg, tosgp());
			}
			else
				terror ("for SY_MINUS", typeid);
			break;
		}
		as_emit1();
		break;

	case SY_STAR:
		switch (typeid) {

		case TW_INT:
			reg = popgp();
			sprintf (t->line, "\tmulw2\t%%r%d,%%r%d", reg, tosgp());
			break;

		case TW_FLOAT:
			reg = popfp();
			sprintf (t->line, "\tmfmuls2\t%%f%d,%%f%d", reg, tosfp());
			break;

		case TW_DOUBLE:
			reg = popfp();
			sprintf (t->line, "\tmfmuld2\t%%f%d,%%f%d", reg, tosfp());
			break;

		default:
			terror ("for SY_STAR", typeid);
			break;
		}
		as_emit1();
		break;
			
	case SY_SLASH:
	case SY_DIV:
		switch (typeid) {

		case TW_INT:
			reg = popgp();
			sprintf (t->line, "\tdivw2\t%%r%d,%%r%d", reg, tosgp());
			break;

		case TW_FLOAT:
			reg = popfp();
			sprintf (t->line, "\tmfdivs2\t%%f%d,%%f%d", reg, tosfp());
			break;

		case TW_DOUBLE:
			reg = popfp();
			sprintf (t->line, "\tmfdivd2\t%%f%d,%%f%d", reg, tosfp());
			break;

		default:
			terror ("for SY_DIV", typeid);
			break;
		}
		as_emit1();
		break;
			
	case SY_MOD:

		/*
		 * In Pascal, "a mod b" requires that b be positive and that
		 * the resulting value be non negative.
		 */

		if (typeid != TW_INT)
			terror ("for SY_MOD", typeid);
		reg = popgp();
		sprintf (t->line, "\tcmpw\t%%r%d,&0", reg);
		as_emit2();
		sprintf (t->line, "\tjge\t.LL%d", mylabel);
		as_emit2();
		strcpy(t->line, "\tcall\t&0,RT_xfloat");
		as_emit2();
		sprintf (t->line, ".LL%d:", mylabel++);
		as_emit2();
		sprintf (t->line, "\tmodw2\t%%r%d,%%r%d", reg, tosgp());
		as_emit2();
		sprintf (t->line, "\tcmpw\t%%r%d,&0", tosgp());
		as_emit2();
		sprintf (t->line, "\tjge\t.LL%d", mylabel);
		as_emit2();
		sprintf (t->line, "\taddw2\t%%r%d,%%r%d", reg, tosgp());
		as_emit2();
		sprintf (t->line, ".LL%d:", mylabel++);
		as_emit2();
		break;

	case ND_LSHIFT:
		if (typeid != TW_INT)
			terror ("for ND_LSHIFT", typeid);
		reg = popgp();
		sprintf (t->line, "\tllsw2\t%%r%d,%%r%d",
			reg, tosgp());
		as_emit1();
		break;
			
	case ND_UMINUS:
		switch (typeid) {

		case TW_INT:
			sprintf (t->line, "\tmnegw\t%%r%d,%%r%d", tosgp(), tosgp());
			break;

		case TW_FLOAT:
			sprintf (t->line, "\tmfnegs1\t%%f%d", tosfp());
			break;

		case TW_DOUBLE:
			sprintf (t->line, "\tmfnegd1\t%%f%d", tosfp());
			break;

		default:
			terror ("for ND_UMINUS", typeid);
			break;
		}
		as_emit1();
		break;

	case ND_ABS:
		switch (typeid) {

		case TW_FLOAT:
			sprintf (t->line, "\tmfabss1\t%%f%d", tosfp());
			as_emit1();
			break;
 
		case TW_DOUBLE:
			sprintf (t->line, "\tmfabsd1\t%%f%d", tosfp());
			as_emit1();
			break;

		case TW_INT:
			sprintf (t->line, "\tcmpw\t%%r%d,&0", tosgp());
			as_emit2();
			sprintf (t->line, "\tjge\t.LL%d", mylabel);
			as_emit2();
			sprintf (t->line, "\tmnegw\t%%r%d,%%r%d", tosgp(), tosgp());
			as_emit2();
			sprintf (t->line, ".LL%d:", mylabel++);
			as_emit2();
			break;

		default:
			terror ("for ND_ABS", typeid);
			break;
		}
		break;

	case ND_SQR:

		switch (typeid) {

		case TW_INT:
			sprintf (t->line, "\tmulw2\t%%r%d,%%r%d",
				tosgp(), tosgp());
			break;

		case TW_FLOAT:
			sprintf (t->line, "\tmfmuls2\t%%f%d,%%f%d",
				tosfp(), tosfp());
			break;

		case TW_DOUBLE:
			sprintf (t->line, "\tmfmuld2\t%%f%d,%%f%d",
				tosfp(), tosfp());
			break;

		default:
			terror ("for ND_SQR", typeid);
			break;
		}
		as_emit2();
		break;

	case ND_ROUND:
		/*
		 * The MAU instructions mmovdw and mmovsw both round as
		 * they convert to integer.  Cannot move directly from gp
		 * register to fp register so must go through memory (_RTMP).
		 */
		reg = popfp();
		switch (typeid)  {

		case TW_FLOAT:
			sprintf (t->line, "\tmmovsw\t%%f%d,_RTMP", reg); 
			break;

		case TW_DOUBLE:
			sprintf (t->line, "\tmmovdw\t%%f%d,_RTMP", reg); 
			break;

		default:
			terror ("for ND_ROUND", typeid);
			break;
		}
		as_emit1();
		sprintf (t->line, "\tmovw\t_RTMP,%%r%d", pushgp());
		as_emit1();
		break;

	case ND_TRUNC:
		/*
		 * Because the MAU instructions "mmovsw" and "mmovdw" both
		 * round, extra instructions are needed to get the desired
		 * truncation.  Must go through memory (_RTMP).
		 */
		switch(typeid) {

		case TW_FLOAT:
			tchar = 's';
			break;

		case TW_DOUBLE:
			tchar = 'd';
			break;

		default:
			terror ("for ND_TRUNC", typeid);
			break;
		}
		reg = popfp();
		sprintf (t->line, "\tmfrnd%c2\t%%f%d,_RTMP", tchar, reg);
		as_emit2();
		sprintf (t->line, "\tmfcmp%c\t%%f%d,_RTMP", tchar, reg);
		as_emit2();
		sprintf (t->line, "\tmjfe\t.LL%d", mylabel+1);
		as_emit2();
		sprintf (t->line, "\tmfcmp%c\t%%f%d,&0.0", tchar, reg);
		as_emit2();
		sprintf (t->line, "\tmjfl\t.LL%d", mylabel);
		as_emit2();
		sprintf (t->line, "\tmfsub%c2\t&0.5,%%f%d", tchar, reg);
		as_emit2();
		sprintf (t->line, "\tjmp\t.LL%d", mylabel+1);
		as_emit2();
		sprintf (t->line, ".LL%d:", mylabel++);
		as_emit2();
		sprintf (t->line, "\tmfadd%c2\t&0.5,%%f%d", tchar, reg);
		as_emit2();
		sprintf (t->line, ".LL%d:", mylabel++);
		as_emit2();
		sprintf (t->line, "\tmmov%cw\t%%f%d,_RTMP", tchar, reg);
		as_emit1();
		sprintf (t->line, "\tmovw\t_RTMP,%%r%d", pushgp());
		as_emit1();
		break;

	case ND_ODD:
		if (typeid != TW_INT)
			terror ("for ND_ODD", typeid);
		sprintf(t->line, "\tandw2\t&1,%%r%d", tosgp());
		as_emit1();
		break;
				 
	default:
		operror ("cgen: illegal operator (%d) in c_aoper()", opcode);
	}
}
				
c_assign(typeid)
	TWORD typeid;
{
	register int reg;

	reg = popgp();	/* destination address */
	if (typeid == TW_DOUBLE || typeid == TW_FLOAT) {
		sprintf (t->line, "\t%s\t%%f%d,(%%r%d)", movinstr(typeid),
			popfp(), reg);
	}
	else {
		sprintf (t->line, "\t%s\t%%r%d,(%%r%d)", movinstr(typeid),
			popgp(), reg);
	}
	as_emit1();
}
				
c_bgnstmt(n)
	int n;
{
	register int i;

	if (opt_g) {
		if (fstmtno == -1) {   /* new function */
			fstmtno = n - 1;
			funcsym(fullname(fnametab[currlevel-1], currlevel-1),
				functype[currlevel-1], n);
		}
		sprintf (t->line, "\t.ln\t%d", n - fstmtno);
		as_emit2();
	}
	if (stmtno != n) {
		stmtno = n;
		sprintf (t->line, "# BGNSTMT %d", n);
		as_emit2();
		if (!opt_t) {
			sprintf (t->line, "\tmovw\t&%d,_STMTNO", n);
			as_emit2();
		}
	}
	rstk = fstk = -1;
	for (i = 0; i < NUMGREG; i++)
		regbusy[i] = 0;
	for (i = 0; i < NUMFREG; i++)
		fregbusy[i] = 0;
}
				
c_call(typeid, numargs, lenargs)
	TWORD typeid;
	int numargs, lenargs;
{
	register int i; 
	int maxfp;
	void (*emit)();

	/*
	 * If peephole optimization is being used, then emit the call first
	 * before spilling r0 into a new register so the address of the call
	 * can collapse with the call itself.  When spilling r0, then emit it
	 * before the call (as_emit0).
	 */

	if (opeep) {
		sprintf (t->line, "\tcall\t&%d,(%%r%d)", lenargs >> 2, popgp());
		as_emit1();
		emit = as_emit0;
	}
	else
		emit = as_emit1;

	/*
	 *  Save any busy fp registers
	 */
	maxfp = -1;
	for ( i=0; i<3; i++ )
		if (fregbusy[i]) {
			sprintf (t->line, "\tmmovdd\t%%f%d,.T%d+%d(%%fp)",
				i, fnum[currlevel], i<<3);
			(*emit)();
			maxfp = i;
		}
	if (maxfp >= fpregused)
		fpregused = maxfp + 1;
	
	/*
	 * Free up %r0, %r1, %r2 because scratch registers are not saved
	 * by callee and because doubles are returned in %r0 and %r1.
	 * Free registers in opposite order of usage to avoid
	 * saving a register into a register that we are saving next.
	 */

	freegp(2, emit);
	freegp(1, emit);
	freegp(0, emit);
	if (!opeep) {
		sprintf (t->line, "\tcall\t&%d,(%%r%d)", lenargs >> 2, popgp());
		as_emit1();
	}

	/*  Restore any saved floating point registers.  */

	for (i=maxfp; i>=0; i--) {
		sprintf (t->line, "\tmmovdd\t.T%d+%d(%%fp),%%f%d",
			fnum[currlevel], i<<3, i);
		as_emit1();
	}
		
	regbusy[0] = regbusy[1] = regbusy[2] = 0;

	switch (typeid) {

	case TW_UNDEF:
		break;
  
	default:
		if ((typeid & DERIVED) == 0)
			terror ("in c_call()", typeid);
		/* fall through */

	case TW_CHAR:
	case TW_INT: 
		regbusy[0] = 1;
		rstack[++rstk] = 0;	/* push %r0 (return value) */
		break;

	case TW_FLOAT: case TW_DOUBLE:
		/*
		 * Value returned via %r0 and %r1 and should be
		 * moved to a float reg via memory location _RTMP.
		 */
		strcpy (t->line, "\tmovw\t%r0,_RTMP");
		as_emit1();
		if (typeid == TW_FLOAT)  {
			sprintf (t->line, "\tmmovss\t_RTMP,%%f%d", pushfp());
			as_emit1();
		}
		else  {  /* typeid == TW_DOUBLE  */
			strcpy (t->line, "\tmovw\t%r1,_RTMP+4");
			as_emit1();
			sprintf (t->line, "\tmmovdd\t_RTMP,%%f%d", pushfp());
			as_emit1();
		}
		break;
	}
}


c_casel(start, range)
	int start, range;
{
	if (start != 0) {
		sprintf (t->line, "\tsubw2\t&%d,%%r0", start);
		as_emit1();
	}
	defaultlab = mylabel++;
	strcpy (t->line, "\tcmpw\t%r0,&0");
	as_emit2();
	sprintf (t->line, "\tjl\t.LL%d", defaultlab);
	as_emit2();
	sprintf (t->line, "\tcmpw\t%%r0,&%d", range );
	as_emit2();
	sprintf (t->line, "\tjg\t.LL%d", defaultlab);
	as_emit2();
	strcpy (t->line, "\tllsw2\t&2,%r0");
	as_emit2();
	sprintf (t->line, "\tjmp\t*.LL%d(%%r0)", mylabel);
	as_emit2();
	strcpy (t->line, "\t.data");
	as_emit2();
	sprintf (t->line, "\t.align\t%d", AL_ANY);
	as_emit2();
	sprintf (t->line, ".LL%d:", mylabel++);
	as_emit2();
}

c_ckrange1(typeid)
	TWORD typeid;
{
	register int lower, upper, reg;
	upper = popgp();
	lower = popgp();
	reg = tosgp();
	sprintf (t->line, "\tcmpw\t%%r%d,%%r%d", reg, lower);
	as_emit2();
	sprintf (t->line, "\tjl\t.LL%d", mylabel);
	as_emit2();
	sprintf (t->line, "\tcmpw\t%%r%d,%%r%d", reg, upper);
	as_emit2();
	sprintf (t->line, "\tjle\t.LL%d", mylabel+1);
	as_emit2();
	sprintf (t->line, ".LL%d:", mylabel++);
	as_emit2();
	sprintf (t->line, "\tpushw\t%%r%d", reg);
	as_emit2();
	strcpy(t->line, "\tcall\t&1,RT_range_err");
	as_emit2();
	sprintf (t->line, ".LL%d:", mylabel++);
	as_emit2();
}

c_ckrange3(typeid, lower, upper)
	TWORD typeid;
	int lower, upper;
{
	register int reg;
	reg = tosgp();
	sprintf (t->line, "\tcmpw\t%%r%d,&%d", reg, lower);
	as_emit2();
	sprintf (t->line, "\tjl\t.LL%d", mylabel);
	as_emit2();
	sprintf (t->line, "\tcmpw\t%%r%d,&%d", reg, upper);
	as_emit2();
	sprintf (t->line, "\tjle\t.LL%d", mylabel+1);
	as_emit2();
	sprintf (t->line, ".LL%d:", mylabel++);
	as_emit2();
	sprintf (t->line, "\tpushw\t%%r%d", reg);
	as_emit2();
	strcpy(t->line, "\tcall\t&1,RT_range_err");
	as_emit2();
	sprintf (t->line, ".LL%d:", mylabel++);
	as_emit2();
}
	 
c_cmpeq(typeid, n, l)
	TWORD typeid;
	int n, l;
{
	if (typeid != TW_INT)
		terror ("in c_cmpeq()", typeid);
	sprintf (t->line, "\tcmpw\t%%r0,&%d", n);
	as_emit2();
	sprintf (t->line, "\tje\t.L%d", l);
	as_emit2();
}

c_cmpgt(typeid, n, l)
	TWORD typeid;
	int n, l;
{
	if (typeid != TW_INT)
		terror ("in c_cmpgt()", typeid);
	sprintf (t->line, "\tcmpw\t%%r0,&%d", n);
	as_emit2();
	sprintf (t->line, "\tjgt\t.L%d", l);
	as_emit2();
}
	 
c_decl(typeid, id, class, size, blklev)
	TWORD typeid;
	int class, size, blklev;
	char *id;
{
	if (*id == '$')   /* compiler generated temporary variable */
		*id = '.';    /* assembler prefers '.' */

	switch (class) {

	case SC_EXTERN:  /*  For later use? */
		sprintf (t->line, "\t.comm\t%s,%d", id, size);
		as_emit2();
		break;

	case SC_EXTDEF:  
		switch (typeid) {

		case TW_CHAR:
			break;

		default:
			sprintf (t->line, "\t.align\t%d", AL_ANY);
			as_emit2();
			break;
		}
		sprintf (t->line, "\t.globl\t%s\n%s:", id, id);
		as_emit2();
		break;

	case SC_PARAM:  /*  Formal parameter declaration  */
		/*
		 *  Set proper displacement from ap.  Note that if
		 *  parm is of TW_CHAR type, want to give address of
		 *  byte, not word, containing character.
		 */
		sprintf (t->line, "\t.set\t%s.%d,%d", id, blklev,
			typeid==TW_CHAR ? argreq+3 : argreq);
		as_emit2();
		argreq += ALIGN(size, AL_ANY);
		break;

	case SC_AUTO:  /*  Local variable declarations  */

		if ((blklev > 1) || *id == '.') {
			sprintf (t->line, "\t.set\t%s.%d,%d", id, blklev, 
				spacereq[blklev]);
			as_emit2();
			spacereq[blklev] += ALIGN(size, AL_ANY);
		}
		else  { /*  Treat level 1 vars as EXTERN objects */ 
			c_sseg(SG_DATA);
			if (typeid != TW_CHAR)  {
				sprintf (t->line, "\t.align\t%d", AL_ANY);
				as_emit2();
				/*
				 * .align directive fails to align properly for 
				 * some instances where size is 1 or 2.  Hence,
				 * following adjustment.
				 */
				if (size < 3)
					size = 4;
			}
			sprintf (t->line, "\t.comm\t%s,%d", id, size);
			as_emit2();
		}
	}
}

c_deref(typeid)
	TWORD typeid;
{
	if (typeid == TW_DOUBLE || typeid == TW_FLOAT)
		sprintf (t->line, "\t%s\t(%%r%d),%%f%d", movinstr(typeid),
			popgp(), pushfp());
	else
		sprintf (t->line, "\t%s\t(%%r%d),%%r%d", movinstr(typeid),
			tosgp(), tosgp());
	as_emit1();
}

c_efunc(x)
	int x;
{
	int displayce;

	sprintf (t->line, ".LL%d:", fnum[currlevel]);
	as_emit1();

	/*
	 *  Restore former display values, saved regs, & return.
	 */
	displayce = (currlevel << 3);
	sprintf (t->line, "\tmovw\t4(%%fp),_DISPLAY+%d", displayce);
	as_emit2();
	sprintf (t->line, "\tmovw\t8(%%fp),_DISPLAY+%d", displayce+4);
	as_emit2();
	if (fpregused > 0) {
		sprintf (t->line, "\t.set\t.T%d,%d",  fnum[currlevel], 
			spacereq[currlevel]);
		as_emit2();
	}
	/*
	 * Provide stack space for saving floating point registers
	 * and for the pseudo fp registers.  Register usage info comes
	 * from putinst() in peep.c
	 */
	sprintf (t->line, "\t.set\t.F%d,%d",  fnum[currlevel], 
		spacereq[currlevel] + SZ_DOUBLE * fpregused);
	as_emit2();
	sprintf (t->line, "\t.set\t.R%d,%d", fnum[currlevel], gpregused);
	as_emit2();

	/* be sure return value is 0 when returning from "main" */

	if (currlevel == 1) {
		strcpy(t->line, "\tmovw\t&0,%r0");
		as_emit2();
	}
	sprintf (t->line, "\tret\t&.R%d", fnum[currlevel]);
	as_emit2();
	currlevel--;
	if (opt_g)
		efuncsym(fullname(fnametab[currlevel], currlevel),
			stmtno - fstmtno);
}

c_end()
{
}

c_epdef()
{
	register char *p;
	int displayce;

	/*
	 *  End of a declaration section.  Emit entry point
	 *  and prolog for function whose algorithm section
	 *  begins.
	 */

	gpregused = 0;
	fpregused = 0;
	fstmtno = -1;

	/* main declares variable for moving doubles around */

	if (currlevel == 1) {
		c_decl(TW_DOUBLE, "_RTMP", SC_AUTO, 8, 1);
	}

	c_sseg(SG_PROG);
	p = fullname(fnametab[currlevel-1], currlevel-1);
	sprintf (t->line, "\t.globl\t%s", p);
	as_emit2();
	sprintf (t->line, "%s:", p);
	as_emit2();

	/* save caller's registers */

	sprintf (t->line, "\tsave\t&.R%d", fnum[currlevel]);
	as_emit2();
	/*
	 * if profiling, emit code to count calls to this subroutine.
	 * Caution:  this code must be emitted immediately after
	 * the "save" instruction
	 */
	if (opt_p) {
		strcpy(t->line, "\t.data");
		as_emit2();
		sprintf(t->line, "\t.align\t%d", AL_ANY);
		as_emit2();
		sprintf(t->line, ".LL%d:", mylabel);
		as_emit2();
		strcpy(t->line, "\t.word\t0");
		as_emit2();
		strcpy(t->line, "\t.text");
		as_emit2();
		sprintf(t->line, "\tmovw\t&.LL%d,%%r0", mylabel++);
		as_emit2();
		strcpy(t->line, "\tjsb\t_mcount");
		as_emit2();
	}

	/* bump stack pointer to get space for local variables */

	sprintf (t->line, "\taddw2\t&.F%d,%%sp", fnum[currlevel]);
	as_emit2();

	/* set the current file name */

	if (!opt_t) {
		strcpy(t->line, "\tmovw\t&f.name,_FILENAME");
		as_emit2();
	}

	/*
	 * In 0(%fp) save a pointer to the display entry for this scope
	 * level. The pointer is needed for restoring the display during a
	 * non-local goto.
	 */
	displayce = (currlevel << 3);
	sprintf (t->line, "\tmovw\t&_DISPLAY+%d,0(%%fp)",displayce);
	as_emit2();
	/*
	 * In 4(%fp) and 8(%fp) save the display entry for this nesting level.
	 * (restored upon return).
	 */
	sprintf (t->line, "\tmovw\t_DISPLAY+%d,4(%%fp)",displayce);
	as_emit2();
	sprintf (t->line, "\tmovw\t_DISPLAY+%d,8(%%fp)",displayce+4);
	as_emit2();

	/* make new display entry with current %ap, %fp */

	sprintf (t->line, "\tmovw\t%%ap,_DISPLAY+%d",displayce);
	as_emit2();
	sprintf (t->line, "\tmovw\t%%fp,_DISPLAY+%d",displayce+4);
	as_emit2();


	/* if main routine, call RT_startup */

	if (currlevel == 1) {
		strcpy(t->line, "\tpushw\t(%ap)");
		as_emit2();
		strcpy(t->line, "\tpushw\t4(%ap)");
		as_emit2();
		strcpy(t->line, "\tcall\t&2,RT_startup");
		as_emit2();
	}
}

c_fcon(typeid, class, value)
	TWORD typeid;
	int class;
	double value;
{
	register int *p;
	register void (*emit)();

	switch(typeid) {

	case TW_FLOAT:
		sprintf (t->line, "\tmmovds\t.LL%d,%%f%d", mylabel, pushfp());
		break;

	case TW_DOUBLE:
		sprintf (t->line, "\tmmovdd\t.LL%d,%%f%d", mylabel, pushfp());
		break;

	default:
		terror("in c_fcon()", typeid);
		break;
	}
	as_emit1();
	emit = opeep ? as_emit0 : as_emit1;
	strcpy(t->line, "\t.data");
	(*emit)();
	sprintf(t->line, "\t.align\t%d", AL_ANY);
	(*emit)();
	sprintf(t->line, ".LL%d:", mylabel++);
	(*emit)();
	p = (int *) &value;
	sprintf(t->line, "\t.word\t0x%x,0x%x", *p, *(p+1));
	(*emit)();
	strcpy(t->line, "\t.text");
	(*emit)();
}

c_file(fname)
	char *fname;
{
	register char *f;
	if (f = strrchr(fname, '/'))
		++f;
	else
		f = fname;
	sprintf(t->line, "\t.file\t\"%s\"", f);
	as_emit2();
	c_sseg(SG_DATA);
	strcpy(t->line, "f.name:");
	as_emit2();
	c_string(f);
}


c_func(typeid, id, class, blklevel)
	TWORD typeid;
	int class;
	register char *id;
	int blklevel;
{
	if (blklevel >= OB_MAXLEVEL)
		operror("cgen: nesting level exceeded", 0);
	/*
	 *  Put the function name in the table of currently active
	 *  function names for generating fully qualified names.
	 */
	fnametab[blklevel] = id;
	functype[blklevel] = typeid;

	/* block level increments upon entering new function */

	currlevel = blklevel+1;
	fnum[currlevel] = mylabel++;
	argreq = 0;
	/*
	 *  The amount of space required for locals is held in
	 *  an array indexed by blklevel so that accumulators 
	 *  for previous levels (still being compiled) will not
	 *  be lost.  Initialization here takes into account 
	 *  storage of old display values.
	 */
	spacereq[currlevel] = 12;  
	sprintf (t->line, "#\t%s declarations", id);
	as_emit2();
}


c_goto(n, blklevel)
	int n, blklevel;
{
	if (currlevel == blklevel) {  /* local jump  */
		sprintf (t->line, "\tjmp\t.L%d", n);
		as_emit1();
	}
	else {
		/*
		 * non-local goto -- call run time assembly routine
		 * to fix up stack, etc.
		 */
		sprintf(t->line, "\tpushaw\t.L%d", n);
		as_emit2();
		sprintf(t->line, "\tpushw\t_DISPLAY+%d", blklevel * 8 + 4);
		as_emit2();
		strcpy(t->line, "\tcall\t&2,RT_goto");
		as_emit2();
	}
}


c_icon(typeid, class, value)
	TWORD typeid;
	int class, value;
{
	if (class == SC_NULL)
		sprintf (t->line, "\t%s\t&%d,%%r%d", movinstr(typeid),
			value, pushgp());
	else 		  /* class == SC_LABEL */
		sprintf (t->line, "\tmovaw\t.L%d,%%r%d", value, pushgp());
	as_emit1();
}

c_init()
{
}

c_jmp(opcode, typeid, label)
	int opcode, label;
	TWORD typeid;
{
	register int reg;
	if (typeid == TW_DOUBLE || typeid == TW_FLOAT) {
		reg = popfp();
		sprintf (t->line, "\t%s\t%%f%d,%%f%d",
			cmpinstr(typeid), popfp(), reg);
	}
	else {
		reg = popgp();
		sprintf (t->line, "\t%s\t%%r%d,%%r%d",
			cmpinstr(typeid), popgp(), reg);
	}
	as_emit1();
	sprintf (t->line, "\t%s\t.L%d", jmpinstr(opcode), label);
	as_emit1();
}

c_label(n)
	int n;
{
	sprintf (t->line, ".L%d:", n);
	as_emit1();
}

c_pconv(oldtypeid, newtypeid)
	TWORD oldtypeid, newtypeid;
{
}
				
c_pusha(typeid)
	TWORD typeid;
{
	sprintf (t->line, "\tpushw\t%%r%d", popgp());
	as_emit1();
}

c_pushv(typeid)
	TWORD typeid;
{
	switch (typeid) {

	case TW_CHAR:
	case TW_INT:
		sprintf (t->line, "\tpushw\t%%r%d", popgp());
		as_emit1();
		break;

	case TW_FLOAT:
	case TW_DOUBLE:
		if (!opeep) {
			strcpy (t->line, "\taddw2\t&8,%sp");
			as_emit1();
		}
		if (typeid == TW_FLOAT)
			sprintf (t->line, "\tmmovsd\t%%f%d,-8(%%sp)", popfp());
		else
			sprintf (t->line, "\tmmovdd\t%%f%d,-8(%%sp)", popfp());
		as_emit1();
		if (opeep) {
			strcpy (t->line, "\taddw2\t&8,%sp");
			as_emit0();
		}
		break;

	default:
		if ((typeid & DERIVED) != 0) {
			sprintf (t->line, "\tpushw\t%%r%d", popgp());
			as_emit1();
		}
		else
			terror ("in c_pushv()", typeid);
		break;  
	}
}

c_reasg0(typeid)
	TWORD typeid;
{
}

c_rel(opcode, typeid)
	int opcode;
	TWORD typeid;
{
	register int reg;

	/* pop operands, compare and jump */

	if (typeid == TW_DOUBLE || typeid == TW_FLOAT) {
		reg = popfp();
		sprintf (t->line, "\t%s\t%%f%d,%%f%d",
			cmpinstr(typeid), popfp(), reg);
	}
	else {
		reg = popgp();
		sprintf (t->line, "\t%s\t%%r%d,%%r%d",
			cmpinstr(typeid), popgp(), reg);
	}
	as_emit1(); 
	sprintf (t->line, "\t%s\t.LL%d", jmpinstr(opcode), mylabel);
	as_emit1();

	/* push 0 or 1 on top of the register stack */

	reg = pushgp();
	sprintf (t->line, "\tmovw\t&0,%%r%d", reg);
	as_emit2();
	sprintf (t->line, "\tjmp\t.LL%d", mylabel+1);
	as_emit2();
	sprintf (t->line, ".LL%d:", mylabel++);
	as_emit2();
	sprintf (t->line, "\tmovw\t&1,%%r%d", reg);
	as_emit2();
	sprintf (t->line, ".LL%d:", mylabel++);
	as_emit2();
}
				
c_return(typeid)
	TWORD typeid;
{
	switch (typeid) {

	case TW_UNDEF:
		break;

	case TW_CHAR:
	case TW_INT:
		if (tosgp() != 0) {
			sprintf (t->line, "\tmovw\t%%r%d,%%r0", popgp());
			as_emit1();
		}
		break;

	case TW_FLOAT:  
	case TW_DOUBLE:

		/*  Move from float reg to gen reg(s) via _RTMP */

		if (typeid == TW_FLOAT)  
			sprintf (t->line, "\tmmovss\t%%f%d,_RTMP", popfp());
		else
			sprintf (t->line, "\tmmovdd\t%%f%d,_RTMP", popfp());
		as_emit1();

		strcpy (t->line, "\tmovw\t_RTMP,%r0");
		as_emit1();

		if (typeid == TW_DOUBLE)  {
			strcpy (t->line, "\tmovw\t_RTMP+4,%r1");
			as_emit1();
		}
		break;

	default:
		if ((typeid & DERIVED) != 0) {
			if (tosgp() != 0) {
				sprintf (t->line, "\tmovw\t%%r%d,%%r0", popgp()); 
				as_emit1();
			}
		}
		else
			terror ("in c_return()", typeid);
		break;
	}
	/*
 	 *  Emit jump to function epilog.  Note the emission of the return
 	 *  instruction is handled in c_efunc.
 	 */
	sprintf (t->line, "\tjmp\t.LL%d", fnum[currlevel]);
	as_emit1();
}

c_reuse0(typeid)
	TWORD typeid;
{
}
				
c_sconv(oldtypeid, newtypeid)
	TWORD oldtypeid, newtypeid;
{
	switch (oldtypeid) {

	case TW_CHAR:
		switch (newtypeid) {

		case TW_CHAR:
		case TW_INT:
			break;

		default:
			terror ("in c_sconv()", newtypeid);
			break;  
		}
		break;

	case TW_INT:
		switch (newtypeid) {

		case TW_CHAR:
		case TW_INT:
			break;

		case TW_FLOAT:

			/* cannot move directly from gp register to fp register */

			sprintf (t->line, "\tmovw\t%%r%d,_RTMP", popgp());
			as_emit1();
			sprintf (t->line, "\tmmovws\t_RTMP,%%f%d", pushfp());
			as_emit1();
			break;

		case TW_DOUBLE:
			sprintf (t->line, "\tmovw\t%%r%d,_RTMP", popgp());
			as_emit1();
			sprintf (t->line, "\tmmovwd\t_RTMP,%%f%d", pushfp());
			as_emit1();
			break;

		default:
			if ((newtypeid & DERIVED) == 0)
				terror ("c_sconv()", newtypeid);
			break;  
		}
		break;

	case TW_FLOAT:
		switch (newtypeid) {

		case TW_FLOAT:
			break;

		case TW_DOUBLE:
			sprintf (t->line, "\tmmovsd\t%%f%d,%%f%d", tosfp(),
				tosfp());
			as_emit1();
			break;

		default:
			terror ("in c_sconv()", newtypeid);
			break;  
		}
		break;

	case TW_DOUBLE:
		switch (newtypeid) { /* new typeid */

		case TW_FLOAT:
			sprintf (t->line, "\tmmovds\t%%f%d,%%f%d", tosfp(),
				tosfp());
			as_emit1();
			break;

		case TW_DOUBLE:
			break;

		default:
			terror ("in c_sconv()", newtypeid);
			break;  
		}
		break;

	default:
		if ((oldtypeid & DERIVED) == 0)
			terror ("in c_sconv()", oldtypeid);
		break;  
	}
}

c_setc()
{
	if (!opeep) {
		strcpy (t->line, "# SETC");
		as_emit1();
	}
}
			
c_sseg(n)
	int n;
{
	if (n != SG_PROG)
		n = SG_DATA;
	if (n != segment) {
		segment = n;
		strcpy(t->line, n == SG_PROG ? "\t.text" : "\t.data");
		as_emit2();
	}
}


c_starg(typeid, n)
	TWORD typeid;
	int n;
{
	/*
	 *  %r0 <- &source
	 *  %r1 <- &destination
	 *  %r2 <- # of bytes to move
	 *  movblb (move block of bytes)
	 */
	if (tosgp() != 0) {
		freegp(0, as_emit1);
		sprintf (t->line, "\tmovw\t%%r%d,%%r0", popgp());
		as_emit1();
	}
	else
		regbusy[popgp()] = 1;
	freegp(1, as_emit1);
	strcpy (t->line, "\tmovw\t%sp,%r1");
	as_emit1();
	freegp(2, as_emit1);
	sprintf (t->line, "\tmovw\t&%d,%%r2", n);
	as_emit1();
	sprintf (t->line, "\taddw2\t&%d,%%sp", ALIGN(n, AL_ANY));
	as_emit1();
	strcpy (t->line, "\tmovblb");
	as_emit1();
	regbusy[0] = regbusy[1] = regbusy[2] = 0;
}

c_stasg(typeid)
	TWORD typeid;
{
	/*
	 *  %r0 <- &source
	 *  %r1 <- &destination
	 *  %r2 <- # of bytes to move
	 *  movblb  (move block of bytes)
	 */
	register int reg;
	for (reg = 2; reg >= 0; reg--) {
		if (tosgp() != reg) {	/* must move top of stack to reg */
			freegp(reg, as_emit1);
			sprintf (t->line, "\tmovw\t%%r%d,%%r%d", popgp(), reg);
			as_emit1();
		}
		else
			regbusy[popgp()] = 1;
	}
	strcpy (t->line, "\tmovblb");
	as_emit1();
	regbusy[0] = regbusy[1] = regbusy[2] = 0;
}

c_string(s)
	register char *s;
{
	register char *p;
	register int i;
	do {
		strcpy(t->line, "\t.byte\t");
		p = t->line + 7;
		for (i=0; i < 16 && *s; ++i)
			p += sprintf(p, "%d,", *s++);
		if (*s) {
			*(p-1) = '\0';
			as_emit2();
		}
	} while (*s);
	*p++ = '0';
	*p = '\0';
	as_emit2();
}
		
c_switchs(typeid, n, s, l)
	TWORD typeid;
	int n, s, l;
{
	slabels = l + 1;  /*  remember no. of jump table entries  */
}
				

c_switchv(typeid)
	TWORD typeid;
{
	register int reg;
	if (typeid != TW_INT)  /* front end converts case index to int */
		terror ("in c_switchv()", typeid);
	reg = popgp();
	if (regbusy[0])
		operror ("cgen: %r0 busy in c_switchv()", 0);
	if (reg != 0) {
		sprintf(t->line, "\tmovw\t%%r%d,%%r0", reg);
		as_emit1();
	}
}
				
c_use0(typeid)
	TWORD typeid;
{
	(void) popgp();
}
				
c_word(n)
	int n;
{
	sprintf (t->line, "\t.word\t.L%d", n);
	as_emit2();
	if (--slabels == 0)  {
		/*  Check to see if can switch back to text segment  */
		strcpy (t->line, "\t.text");
		as_emit2();
		sprintf (t->line, ".LL%d:", defaultlab);
		as_emit2();
	}
}
