/* Copyright (c) 1988 by Sozobon, Limited.  Author: Johann Ruegg
 *
 * Permission is granted to anyone to use this software for any purpose
 * on any computer system, and to redistribute it freely, with the
 * following restrictions:
 * 1) No charge may be made other than reasonable charges for reproduction.
 * 2) Modified versions must be clearly marked as such.
 * 3) The authors are not responsible for any harmful consequences
 *    of using this software, even if they result from defects in it.
 *
 *	gen.c
 *
 *	Generate code.
 *	Includes main routine and code generation for unary nodes
 *	and leafs.
 *   Revised: Dec 1988	Joe Montgomery
 *
 *   Revised gen.c to call externfunref to declare all functions XREF
 *
 *     other modules:
 *   Revised main.c to use Amiga File System Naming Conventions
 *	Added ?,C,F switches. ? help
 *			      C force data,bss into Chip memory
 *			      F force data,bss into Fast memory
 *	To be added -o switch to specify assembly output
 *   Revised out.c to use MOTOROLA assembly directives in order
 *	to be compatible with C.Gibbs a68k assembler & blink
 *	Added END statement
 *	Changed .comm label,size to label DC.x 0
 *   Revised d2.c so that externs are declared as XREF -----
 *   Revised g2.c & gen.c to declare all called functions XREF
 *     (will need to change this to declare only external functions)
 *
 *
 *   All changes labeled JMM
 *
 */

#include <stdio.h>
#include "param.h"
#include "bstok.h"
#include "tytok.h"
#include "flags.h"
#include "nodes.h"
#include "gen.h"

NODEP strsave;
int cctest;
static int reserve;
static int tmpused;

extern xflags[];
#define debug xflags['g'-'a']

#define FAIL	0
#define OKAY	1

#define isimmed(np)     ((np)->g_flags & IMMEDID)
#define isareg(np)      ((np)->g_token == REGVAR && (np)->g_rno >= AREG)
#define isdreg(np)      ((np)->g_token == REGVAR && (np)->g_rno < AREG)
#define istdreg(np)     ((np)->g_token == REGVAR && (np)->g_rno < DRV_START)

int cookflags[] = {
	0,
	NOVAL_OK|CC_OK|IMMA_OK, 	/* FORSIDE */
	IMMA_OK,			/* FORPUSH */
	CC_OK,				/* FORCC */
	IMMA_OK,			/* FORIMA */
	0,				/* FORADR */
	IMMA_OK,			/* FORINIT */
	0,				/* IND0 */
	0,				/* RETSTRU */
};



extern NODE *blktab;
extern nmerrors;

genx(np, cookie)
register NODEP np;
{
	int rv;

	if (np == NULL) return;
	if (nmerrors)
		goto bad;
	if (debug) {
		printf("GEN enter");
		printnode(np);
	}

	untype(np);
	if (debug>1) {
		printf("after UNTYPE");
		printnode(np);
	}

	tmpused = 0;
	gunk(np);
	if (tmpused && tmpused > blktab->b_tsize)
		blktab->b_tsize = tmpused;
	if (debug > 1) {
		printf("after gunk");
		printnode(np);
	}
	order(np);

	reserve = 0;
	rv = eval(np, cookie);
	if (rv == OKAY && debug) {
		printf("GEN exit");
		codeprint(np);
	}
	if (rv == OKAY)
		rv = cookmon(np, cookie);
	else
		error("couldnt eval node");
	freenode(np);
	return rv;
bad:
	freenode(np);
	return FAIL;
}

eval(np, cookie)
register NODEP np;
{
	int rv;

	np->g_r1 = np->g_r2 = -1;

	if (np == NULL) return FAIL;

	switch (np->g_type) {

	default:
		rv = b_eval(np, cookie);
		/* already did freetmps */
		free1(np, np->n_left);
		free1(np, np->n_right);
		break;

	case EV_LEFT:
		rv = u_eval(np, cookie);
		freetmps(np);
		free1(np, np->n_left);
		break;

	case EV_NONE:
		rv = l_eval(np);
		break;
	}
	return rv;
}

u_eval(np, cookie)
register NODEP np;
{
	int subcook = FORADR;

	switch (np->g_token) {
	case STAR:
		subcook = FORIMA;
		break;
	case '!':
		subcook = FORCC;
		break;
	}

	if (eval(np->n_left, subcook) == FAIL)
		return FAIL;
	return u_sube(np, cookflags[cookie]);
}

u_sube(np, flags)
register NODEP np;
{
	register NODEP lp = np->n_left;
	long offs;
	int i;

	switch (np->g_token) {
	case '.':
		if (np->g_fldw)
			return fldget(np, flags);
		offs = np->g_offs;
		inherit(np);
		np->g_offs += offs;
		return OKAY;
	case STAR:
		if (isimmed(lp)) {
			inherit(np);
			np->g_flags &= ~IMMEDID;
		} else if (isareg(lp)) {
			indir(np, lp->g_rno);
		} else {	/* NEED A temp */
			if (lp->g_token == OREG && istemp(lp->g_rno))
				i = lp->g_rno;
			else
				i = ralloc(AREG);
			addcode(np, "\tmove.l\t<A,R0\n");
			indir(np, i);
		}
		return OKAY;
	case UNARY '&':
		if (isimmed(lp))
			warn("& ignored");
		else if (lp->g_token == REGVAR)
			return FAIL;
		inherit(np);
		np->g_flags |= IMMEDID;
		if ((flags & IMMA_OK) == 0)
			imm_oreg(np);
		return OKAY;
	case '~':
		utemp(np);
		addcode(np, "\tnot.S\tA\n");
		cc_hack(np);
		return OKAY;
	case UNARY '-':
		utemp(np);
		addcode(np, "\tneg.S\tA\n");
		cc_hack(np);
		return OKAY;
	case TCONV:
		castgen(np);
		return OKAY;
	case PUSHER:	/* must not be on left of assign or asn-op */
		if ((lp->g_token != OREG && lp->g_token != REGVAR) ||
			istemp(lp->g_rno) == 0) {
			inherit(np);
			return OKAY;
		}
		addcode(np, "\tmove.S\t<A,-(sp)\n");
		return OKAY;
	case '(':
		if (np->g_ty == ET_A) {         /* struct returned */
			frc_ral(AREG);
			indir(np, AREG);
		} else {
			frc_ral(0);
			retreg(np, 0);
		}
    /* JMM
	  ? added XREF statement.  Note I use this regardless of whether
       the function is defined in the module or not.  This is horrible
       and may cause problems.	I will correct this when I can determine
       whether the function is defined in the current module or is an
       external reference. ?
     */ 	externfuncref(np); /* see out.c */

		addcode(np, "\tjsr\t<A\n");
		return OKAY;
	case DOUBLE '+':
		holdcon(np);
		inherit(np);
		addcode(np, "\tadd.S\t#K,A\n");
		cc_hack(np);
		return OKAY;
	case DOUBLE '-':
		holdcon(np);
		inherit(np);
		addcode(np, "\tsub.S\t#K,A\n");
		cc_hack(np);
		return OKAY;
	case POSTINC:
		if ((flags & NOVAL_OK) == 0) {
			i = ralloc(0);
			retreg(np, i);
			addcode(np, "\tmove.S\t<A,A\n");
		}
		addcode(np, "\tadd.S\t#O,<A\n");
		return OKAY;
	case POSTDEC:
		if ((flags & NOVAL_OK) == 0) {
			i = ralloc(0);
			retreg(np, i);
			addcode(np, "\tmove.S\t<A,A\n");
		}
		addcode(np, "\tsub.S\t#O,<A\n");
		return OKAY;
	case CMPBR:
		i = ralloc(0);
		retreg(np, i);
		addcode(np, "\tsN\tA\n\tand.w\t#1,A\n");
		cc_hack(np);
		return OKAY;
	case '!':
		if (flags & CC_OK) {
			if (iscc(lp)) {
				i = cctok(lp);
				i = (i&1) ? i+1 : i-1;  /* reverse truth */
			} else {
				i = B_EQ;
				addcode(np, "<Q");
			}
			np->g_token = i + BR_TOK;
		} else {
			if (istdreg(lp))
				i = lp->g_rno;
			else
				i = ralloc(0);
			retreg(np, i);
			if (!iscc(lp))
				addcode(np, "<Q");
			addcode(np, "\tseq\tA\n\tand.w\t#1,A\n");
		}
		return OKAY;
	default:
		printf("Weird u_eval %s ", np->n_name);
		return FAIL;
	}
}

holdcon(np)
NODEP np;
{
	np->g_bsize = np->g_offs;
}

retreg(np, rno)
NODEP np;
{
	np->g_token = REGVAR;
	np->g_rno = rno;
}

indir(np, rno)
register NODEP np;
{
	np->g_token = OREG;
	np->g_offs = 0;
	np->g_rno = rno;
}

inherit(np)
register NODEP np;
{
	NODEP lp = np->n_left;

	np->g_token = lp->g_token;
	np->g_offs = lp->g_offs;
	np->g_rno = lp->g_rno;
	np->g_flags |= CHILDNM | (lp->g_flags & IMMEDID);
}

extern FILE *output;

cookmon(np, cookie)
register NODEP np;
{

	if (np == NULL) return FAIL;

	switch (cookie) {
	case FORCC:
		if (iscc(np)) {
			outcode(np);
			cctest = cctok(np);
		} else {
			if (np->g_token == ICON && isimmed(np)) {
				cctest = np->g_offs ? B_YES : B_NO;
				return OKAY;
			}
			outcode(np);
			outsub("Q", np);
			cctest = B_NE;
		}
		return OKAY;
	case FORINIT:
		if (anycode(np) == 0 && (np->g_flags & IMMEDID)) {
			out_a(np, output);
			return OKAY;
		}
		error("bad INIT expr");
		return FAIL;
	case IND0:
		outcode(np);
		if (np->g_token != REGVAR ||
			np->g_rno != 0)
			if (np->g_token == ICON && np->g_offs == 0 &&
				isimmed(np))
				outsub("\tclr.S\td0\n", np);
			else
				outsub("\tmove.S\tA,d0\n", np);
		return OKAY;
	case RETSTRU:
		outcode(np);
		strret(np);
	}
	outcode(np);
	return OKAY;
}

anycode(np)
register NODEP np;
{
	if (np->g_code)
		return 1;
	switch (np->g_type) {
	case EV_NONE:
		return 0;
	case EV_LEFT:
		return anycode(np->n_left);
	case EV_RIGHT:
		return anycode(np->n_right);
	case EV_LR:
	case EV_RL:
		return anycode(np->n_left) || anycode(np->n_right);
	}
}

l_eval(np)
register NODEP np;
{
	int l1;

	switch (np->g_token) {
	case ID:
		switch (np->g_sc) {
		default:	/* EXTERN or HERE */
			np->g_token = ONAME;
			np->g_offs = 0;
			if (np->n_name[0] != '%')
				nnmins(np, "_");
			else	/* hack for inline name */
				strcpy(np->n_name, &np->n_name[1]);
			return OKAY;		/* dont free n_nmx */
		case K_STATIC:
			sprintf(np->n_name, "L%d", (int)np->g_offs);
			np->g_offs = 0;
			np->g_token = ONAME;
			break;
		case K_AUTO:
			np->g_rno = AREG+6;
			np->g_token = OREG;
			break;
		case K_REGISTER:
			np->g_token = REGVAR;
			break;
		}
		if (np->n_nmx) {
			freenode(np->n_nmx);
			np->n_nmx = NULL;
		}
		return OKAY;
	case ICON:
		np->g_flags |= IMMEDID;
		return OKAY;
	case FCON:
		np->g_flags |= IMMEDID;
		return OKAY;
	case SCON:
		np->g_flags |= IMMEDID;
		np->g_token = ONAME;
		l1 = new_lbl();
		save_scon(np, l1);
		sprintf(np->n_name, "L%d", l1);
		return OKAY;
	case OREG:
		return OKAY;
	}
	return FAIL;
}

save_scon(np, lbl)
NODEP np;
{
	NODEP tp, copyone();

	tp = copyone(np);
	tp->g_offs = lbl;
	if (np->n_nmx) {
		freenode(np->n_nmx);
		np->n_nmx = NULL;
	}
	putlist(&strsave, tp);
}

utemp(np)
NODEP np;
{
	NODEP lp = np->n_left;
	int i;

	if (lp->g_token == REGVAR &&
	    istemp(lp->g_rno)) {
		inherit(np);
		return;
	}
	i = ralloc(0);
	retreg(np, i);
	addcode(np, "\tmove.S\t<A,A\n");
}

freetmps(np)
register NODEP np;
{
	if (np->g_r1 != -1)
		rfree(np->g_r1);
	if (np->g_r2 != -1)
		rfree(np->g_r2);
}

free1(np, cp)
NODEP np, cp;
{
	int cr;

	if (cp->g_token != OREG && cp->g_token != REGVAR)
		return;
	cr = cp->g_rno;
	if (np && cr == np->g_rno &&
		(np->g_token == OREG || np->g_token == REGVAR))
		return;
	if (istemp(cr))
		rfree(cr);
}

istemp(rno)
{
	return (rno < DRV_START ||
		(rno >= AREG && rno < ARV_START));
}

rfree(rno)
{
	reserve &= ~(1<<rno);
}

frc_ral(rno)
{
	int i;

	i = (1<<rno);
	if (reserve & i)
		error("rno reserved! ");
	reserve |= i;
}

tempr(np, type)
NODEP np;
{
	int i;

	i = ralloc(type);
	if (np->g_r1 == -1)
		np->g_r1 = i;
	else
		np->g_r2 = i;
	return i;
}

ralloc(type)
{
	int starti, endi;
	register int i;

	if (type == AREG) {
		starti = AREG;
		endi = ARV_START;
	} else {
		starti = 0;
		endi = DRV_START;
	}
	for (i=starti; i<endi; i++)
		if ((reserve & (1<<i)) == 0) {
			reserve |= (1<<i);
			return i;
		}
	error("Compiler failure - rallo");
	return -1;
}


extern NODE *blktab;
tmp_alloc(sz)
{

	tmpused += sz;
	return blktab->b_size + tmpused;
}

/* fixes nodes with no code or aX is temp that are #d(aX) */
imm_oreg(np)
NODEP np;
{
	char *regnm(), buf[30];
	int i;

	if (np->g_token != OREG)
		return;
	if ((np->g_flags & IMMEDID) == 0)
		return;
	np->g_flags &= ~IMMEDID;
	if (np->g_offs == 0) {          /* #(a0) -> a0 */
		np->g_token = REGVAR;
		return;
	}
	if (istemp(np->g_rno)) {
		holdcon(np);
		addcode(np, "\tadd\t#K,A\n");
		np->g_token = REGVAR;
		return;
	}
	sprintf(buf, "\tlea\t%d(%s),A\n", (int)np->g_offs, regnm(np->g_rno));
	addcode(np, buf);
	i = ralloc(AREG);
	retreg(np, i);
}

castgen(tp)
register NODEP tp;
{
	register NODEP np = tp->n_left;
	int sz = tp->g_sz;
	int i;

	if (np->g_token == ICON && isimmed(np)) {
		if (tp->g_ty == ET_F) {
			tp->g_token = FCON;
			*(float *)&tp->g_offs = (float)np->g_offs;
			tp->g_flags |= IMMEDID;
		} else {
			inherit(tp);
			i_exp(tp, np->g_sz, np->g_ty);
			squish(tp);
		}
	} else if (np->g_token == FCON && isimmed(np)) {
		if (tp->g_ty != ET_F) {
			tp->g_token = ICON;
			tp->g_offs = (long)*(float *)&np->g_offs;
			tp->g_flags |= IMMEDID;
			squish(tp);
		} else {
			inherit(tp);
		}
	} else if (sz > np->g_sz) {
		if (np->g_ty == ET_U) {
			i = ralloc(0);
			retreg(tp, i);
			addcode(tp, "\tclr.S\tA\n\tmove.<S\t<A,A\n");
		} else {
			if (isdreg(np)) {
				inherit(tp);
			} else {
				i = ralloc(0);
				retreg(tp, i);
				addcode(tp, "\tmove.<S\t<A,A\n");
			}
			if (sz == 4 && np->g_sz == 1)
				addcode(tp, "\text.w\tA\n\text.l\tA\n");
			else
				addcode(tp, "\text.S\tA\n");
		}
		return;
	}
	else if (sz < np->g_sz) {
		switch (np->g_token) {
		case ONAME:
		case OREG:
			if (isimmed(np)) {
smfudge:
				i = ralloc(0);
				retreg(tp, i);
				addcode(tp, "\tmove.<S\t<A,A\n");
				return;
			}
			inherit(tp);
			tp->g_offs = np->g_offs + (np->g_sz - sz);
			break;
		case REGVAR:
			if (sz == 1 && np->g_rno >= AREG)
				goto smfudge;
			/* fall through */
		default:
			inherit(tp);
		}
	} else
		inherit(tp);
}

squish(np)
NODEP np;
{
	int neg;

	neg = (np->g_ty == ET_S && np->g_offs < 0);

	switch (np->g_sz) {
	case 1:
		if (neg)
			np->g_offs |= 0xffffff00L;
		else
			np->g_offs &= 0xff;
		break;
	case 2:
		if (neg)
			np->g_offs |= 0xffff0000L;
		else
			np->g_offs &= 0xffffL;
		break;
	}
}

i_exp(np, osz, oty)
NODEP np;
{
	long l;

	if (oty == ET_S && osz < np->g_sz) {
		l = np->g_offs;
		switch (osz) {
		case 1:
			l = (char) l;
			break;
		case 2:
			l = (short) l;
			break;
		}
		np->g_offs = l;
	}
}
