/* Copyright (c) 1988 by Sozobon, Limited.  Author: Tony Andrews
 *
 * 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.
 */

/*
 * Routines dealing with the parsing and output of instructions.
 */

#include "top.h"

static	void	getarg();
static	int	isreg();

/*
 * addinst(bp, op, args) - add an instruction to block 'bp'
 */
void
addinst(bp, op, args)
register BLOCK	*bp;
char	*op, *args;
{
	register INST	*ni;
	register int	i;
	register char	*s;
	char	*arg2 = "";

	if (*op == '\0')	/* no instruction there */
		return;

	ni = (INST *) alloc(sizeof(INST));

	ni->flags = 0;
	ni->opcode = -1;
	ni->next = NULL;
	ni->prev = NULL;
	ni->live = 0;
	ni->rref = ni->rset = 0;

	ni->src.areg = ni->dst.areg = 0;
	ni->src.ireg = ni->dst.ireg = 0;
	ni->src.disp = ni->dst.disp = 0;
	ni->src.amode = ni->dst.amode = NONE;

	/*
	 * Link into the block appropriately
	 */
	if (bp->first == NULL) {
		bp->first = bp->last = ni;
	} else {
		bp->last->next = ni;
		ni->prev = bp->last;

		bp->last = ni;
	}

	for (s = op; *s ;s++) {
		/*
		 * Pseudo-ops start with a period, so the length
		 * specifier can't be the first character.
		 */
		if (*s == '.' && s != op) {	/* length specifier */
			*s++ = '\0';
			switch (*s) {
			case 'b':
				ni->flags |= LENB;
				break;
			case 'w':
				ni->flags |= LENW;
				break;
			case 'l':
				ni->flags |= LENL;
				break;
			default:
				fprintf(stderr, "Bad length spec '%c'\n", *s);
				exit(1);
			}
		}
	}

	for (i=0; opnames[i] ;i++) {
		if (strcmp(op, opnames[i]) == 0) {
			ni->opcode = i;
			break;
		}
	}

	if (ni->opcode < 0) {
		fprintf(stderr, "Unknown op '%s'\n", op);
		exit(1);
	}

	/*
	 * Look for the split between the first and second operands.
	 */
	for (s = args; *s ;s++) {
		/*
		 * skip chars in parens, since an operand split can't
		 * occur within.
		 */
		if (*s == '(') {
			while (*s != ')')
				s++;
		}
		if (*s == ',') {
			*s++ = '\0';
			arg2 = s;
			break;
		}
	}

	getarg(&ni->src, args);
	getarg(&ni->dst, arg2);
}

/*
 * delinst(bp, ip) - delete instruction 'ip' in block 'bp'
 */
void
delinst(bp, ip)
BLOCK	*bp;
register INST	*ip;
{
	register INST	*pi, *ni;	/* previous and next instructions */

	pi = ip->prev;
	ni = ip->next;

	if (pi != NULL)
		pi->next = ni;
	else
		bp->first = ni;

	if (ni != NULL)
		ni->prev = pi;
	else
		bp->last = pi;

	/*
	 * Free space used by the instruction.
	 */
	freeop(&ip->src);
	freeop(&ip->dst);
	free(ip);

	s_idel++;
}

/*
 * getarg(op, s) - parse string 's' into the operand structure 'op'
 *
 * Hack alert!! The following code parses the operands only to the
 * extent needed by the optimizer. We're primarily interested in
 * details about addressing modes used, not in any expressions that
 * might be present. This code is highly tuned to the output of the
 * compiler.
 */
static	void
getarg(op, s)
register struct	opnd	*op;
register char	*s;
{
	extern	long	atol();
	register int	reg;
	register char	*p;

	if (*s == '\0') {
		op->amode = NONE;
		return;
	}

	if (*s == '#') {				/* immediate data */
		op->amode = IMM;
		s += 1;
		if (isdigit(s[0]) || s[0] == '-')
			op->disp  = atol(s);
		else {
			op->amode |= SYMB;
			op->astr = strsave(s);
		}
		return;
	} else if ((reg = isreg(s)) >= 0) {		/* reg. direct */
		op->amode = REG;
		op->areg = reg;
	} else if (s[0] == '(' || (s[0] == '-' && s[1] == '(')) {
		op->amode = REGI;
		if (s[0] == '-') {
			op->amode |= DEC;
			s++;
		}
		s++;		/* skip the left paren */
		if ((op->areg = isreg(s)) < 0) {
			fprintf(stderr, "bad reg. '%s'\n", s);
			exit(1);
		}
		s += 3;		/* skip the register and right paren */

		if (s[0] == '+')
			op->amode |= INC;
	} else if (!isdigit(s[0]) && (s[0] != '-')) {
		op->amode = ABS;
		op->astr = strsave(s);
	} else {
		for (p=s; isdigit(*p) || *p == '-' ;p++)
			;
		if (*p != '(') {
			/*
			 * Must have been absolute, but with an
			 * address instead of a symbol.
			 */
			op->amode = ABS;
			op->astr = strsave(s);
			return;
		}
		*p++ = '\0';
		op->disp = atol(s);
		s = p;
		if (s[0] == 'p' && s[1] == 'c') {	/* PC relative */
			if (s[2] == ')') {
				op->amode = PCD;
				return;
			}
			op->amode = PCDX;
			op->ireg = isreg(s+3);
			if (s[6] == 'l')
				op->amode |= XLONG;
		} else if ((reg = isreg(s)) >= 0) {
			op->areg = reg;
			if (s[2] == ')') {
				op->amode = REGID;
				return;
			}
			op->amode = REGIDX;
			op->ireg = isreg(s+3);
			if (s[6] == 'l')
				op->amode |= XLONG;
		} else {
			fprintf(stderr, "bad reg. '%s' after disp\n", s);
			exit(1);
		}
	}
}

/*
 * characters that can terminate a register name
 */
#define	isterm(c) ((c) == '\0' || (c) == ')' || (c) == ',' || (c) == '.')

static	int
isreg(s)
register char	*s;
{
	if (s[0] == 'd' && isdigit(s[1]) && isterm(s[2]))
		return D0 + (s[1] - '0');
	if (s[0] == 'a' && isdigit(s[1]) && isterm(s[2]))
		return A0 + (s[1] - '0');
	if (s[0] == 's' && s[1] == 'p' && isterm(s[2]))
		return SP;

	return -1;
}


/*
 * Routines for printing out instructions
 */

static	char	*rstr();
static	void	putop();

void
putinst(ip)
register INST	*ip;
{
	char	c;

	fprintf(ofp, "\t%s", opnames[ip->opcode]);

	switch (ip->flags) {
	case LENB:
		c = 'b';
		break;
	case LENW:
		c = 'w';
		break;
	case LENL:
		c = 'l';
		break;
	default:
		c = '\0';
		break;
	}
	if (c)
		fprintf(ofp, ".%c", c);

	if (ip->src.amode != NONE) {
		fprintf(ofp, "\t");
		putop(&ip->src);
	}

	if (ip->dst.amode != NONE) {
		fprintf(ofp, ",");
		putop(&ip->dst);
	}
#ifdef	DEBUG
	if (debug)
		fprintf(ofp, "\t\t* ref(%04x), set(%04x), live(%04x)",
			reg_ref(ip), reg_set(ip), ip->live);
#endif
	fprintf(ofp, "\n");
}

static	void
putop(op)
register struct	opnd	*op;
{
	switch (op->amode & MMASK) {
	case NONE:
		break;
	case REG:
		fprintf(ofp, "%s", rstr(op->areg));
		break;
	case IMM:
		if (op->amode & SYMB)
			fprintf(ofp, "#%s", op->astr);
		else
			fprintf(ofp, "#%ld", op->disp);
		break;
	case ABS:
		fprintf(ofp, "%s", op->astr);
		break;
	case REGI:
		if (op->amode & DEC)
			fprintf(ofp, "-");
		fprintf(ofp, "(%s)", rstr(op->areg));
		if (op->amode & INC)
			fprintf(ofp, "+");
		break;
	case REGID:
		fprintf(ofp, "%ld(%s)", op->disp, rstr(op->areg));
		break;
	case REGIDX:
		fprintf(ofp, "%ld(%s,", op->disp, rstr(op->areg));
		fprintf(ofp, "%s.%c)", rstr(op->ireg),
			(op->amode & XLONG) ? 'l' : 'w');
		break;
	case PCD:
		fprintf(ofp, "%ld(pc)", op->disp);
		break;
	case PCDX:
		fprintf(ofp, "%ld(pc,%s.%c)", op->disp, rstr(op->ireg),
			(op->amode & XLONG) ? 'l' : 'w');
		break;
	default:
		fprintf(stderr, "bad addr. mode in putop: %d\n", op->amode);
		exit(1);
	}
}

static	char *
rstr(r)
register char	r;
{
	static	char	buf[3];

	if (r == SP) {
		buf[0] = 's';
		buf[1] = 'p';
	} else if (r >= A0 && r <= A6) {
		buf[0] = 'a';
		buf[1] = '0' + (r - A0);
	} else {
		buf[0] = 'd';
		buf[1] = '0' + (r - D0);
	}
	buf[2] = '\0';

	return buf;
}

/*
 * opeq(op1, op2) - test equality of the two instruction operands
 */
bool
opeq(op1, op2)
register struct	opnd	*op1, *op2;
{
	if (op1->amode != op2->amode || op1->areg != op2->areg ||
	    op1->ireg  != op2->ireg)
		return FALSE;

	/*
	 * Depending on the addressing mode, we either need to
	 * compare the "astr" strings, or the displacements.
	 */
	if ((op1->amode == ABS) || (op1->amode == (IMM|SYMB))) {
		/* compare strings */
		if (op1->astr == NULL)
			return (op2->astr == NULL);
		else {
			if (op2->astr == NULL)
				return FALSE;
	
			return (strcmp(op1->astr, op2->astr) == 0);
		}
	} else {
		/* compare displacements */
		return (op1->disp == op2->disp);
	}
}
