/* 68020 desciptor table for ACK target optimizer */

MAXOP 2;
PAREN_OPEN "(";
PAREN_CLOSE ")";

%%;

/* useful addressing modes-> */

CONST		{VAL[0] == '#'			};	/* constant */
NUM		{is_number(VAL)			};
A,B		{no_side_effects(VAL)		};
D		{VAL[0] != '#' && !is_areg(VAL)	};	/* not an addr. reg */
X,Y		{TRUE				};
DREG,DREG2 	{is_dreg(VAL)			};	 /* data register */
DSREG		{is_dsreg(VAL)			};	/* data register */
AREG		{is_areg(VAL)			};	/* addressregister */
FPREG,FPREG2	{is_fpreg(VAL)			};	/* fp register */
LAB,L1,L2 	{VAL[0] == 'I'			};	/* label */
NO32	{no_part("div",VAL) && no_part("mul",VAL) && no_part(".l",VAL)};
							/* for move.w ... */
BITNO		{TRUE				};

%%;

/* optimization patterns-> */

/* rewriting rules */
tst X					->	tst.w X ;
cmp X,Y					->	cmp.w X,Y ;

/* special instructions */
move.w #0,D				->	clr.w D ;
move.l #0,D				->	clr.l D ;
move.l #0,AREG				->	sub.l AREG,AREG ;

/* tst-elimination */
add.l #2,sp : tst.w X {no_part("sp",X)}	->	move.w X,(sp)+ ;
add.l #4,sp : tst.l D {no_part("sp",D)}	->	move.l D,(sp)+ ;
add.l #2,sp : move.w X,-(sp)		->	move.w X,(sp) ;
add.l #4,sp : move.l X,-(sp)		->	move.l X,(sp) ;
add.l #4,sp : pea (NUM)			->	move.l #NUM,(sp) ;
add.l #4,sp : pea (AREG)		->	move.l AREG,(sp) ;
add.l #NUM,sp : unlk AREG		->	unlk AREG ;
add.l #NUM,sp : movem.l X,Y : unlk AREG
  {no_part("sp",X) && no_part("sp",Y)}	->	movem.l X,Y : unlk AREG ;
move.w A,X : tst.w A			->	move.w A,X ;
move.w X,A : tst.w A			->	move.w X,A ;
move.l A,D : tst.l A {no_part(D,A)}	->	move.l A,D ;
move.l X,D : tst.l D 			->	move.l X,D ;
move.l A,AREG : tst.l A
		{no_part(AREG,A)}	->	tst.l A : move.l A,AREG ;
move.l X,AREG : move.l AREG,DREG :
	tst.l DREG : beq LAB		->	move.l X,DREG :
						move.l DREG,AREG: beq LAB ;
move.l X,AREG : move.l AREG,DREG :
	tst.l DREG : bne LAB		->	move.l X,DREG :
						move.l DREG,AREG: bne LAB ;

/* redundant move */
move.l DREG,DREG2 : move.l DREG2,DREG	->	move.l DREG,DREG2 ;
move.l DREG,AREG : move.l AREG,DREG	->	move.l DREG,AREG : tst.l DREG ;
move.l AREG,DREG : move.l DREG,AREG	->	move.l AREG,DREG ;
move.w DREG,DREG2 : move.w DREG2,DREG	->	move.w DREG,DREG2 ;
move.l AREG,AREG			->	;

/* register subsumption */
move.w DREG,A : ANY A,X
	{reg_subs_allowed(ANY) &&
	 !is_dreg(A)	}		->	move.w DREG,A : ANY DREG,X ;
move.l DREG,A : ANY A,X
	{reg_subs_allowed(ANY) &&
	 !is_dreg(A)	}		->	move.l DREG,A : ANY DREG,X ;

move.w DREG,DREG2 : NO32 A,DREG2 : move.w DREG2,DREG
		{no_part(DREG2,A)}	->	NO32 A,DREG : move.w DREG,DREG2 ;
move.l DREG,DREG2 : ANY A,DREG2 : move.l DREG2,DREG
		{no_part(DREG2,A)}	->	ANY A,DREG : move.l DREG,DREG2 ;

cmp.b A,A : beq LAB			->	bra LAB ;
cmp.w A,A : beq LAB			->	bra LAB ;
cmp.l A,A : beq LAB			->	bra LAB ;

/* cannot delete cmp's because they affect condition codes (obvious, but ... )*/
cmp.b A,A : bne LAB			->	cmp.b A,A ;
cmp.w A,A : bne LAB			->	cmp.w A,A ;
cmp.l A,A : bne LAB			->	cmp.l A,A ;

/* change some compares to tests */
cmp.w #0,D : beq LAB			->	tst.w D : beq LAB ;
cmp.w #0,D : bne LAB			->	tst.w D : bne LAB ;
cmp.w #0,D : blt LAB			->	tst.w D : blt LAB ;
cmp.w #0,D : ble LAB			->	tst.w D : ble LAB ;
cmp.w #0,D : bge LAB			->	tst.w D : bge LAB ;
cmp.w #0,D : bgt LAB			->	tst.w D : bgt LAB ;

cmp.l #0,D : beq LAB			->	tst.l D : beq LAB ;
cmp.l #0,D : bne LAB			->	tst.l D : bne LAB ;
cmp.l #0,D : blt LAB			->	tst.l D : blt LAB ;
cmp.l #0,D : ble LAB			->	tst.l D : ble LAB ;
cmp.l #0,D : bge LAB			->	tst.l D : bge LAB ;
cmp.l #0,D : bgt LAB			->	tst.l D : bgt LAB ;

cmp.w D,#0 : beq LAB			->	tst.w D : beq LAB ;
cmp.w D,#0 : bne LAB			->	tst.w D : bne LAB ;
cmp.w D,#0 : blt LAB			->	tst.w D : bgt LAB ;
cmp.w D,#0 : ble LAB			->	tst.w D : bge LAB ;
cmp.w D,#0 : bge LAB			->	tst.w D : ble LAB ;
cmp.w D,#0 : bgt LAB			->	tst.w D : blt LAB ;

cmp.l D,#0 : beq LAB			->	tst.l D : beq LAB ;
cmp.l D,#0 : bne LAB			->	tst.l D : bne LAB ;
cmp.l D,#0 : blt LAB			->	tst.l D : bgt LAB ;
cmp.l D,#0 : ble LAB			->	tst.l D : bge LAB ;
cmp.l D,#0 : bge LAB			->	tst.l D : ble LAB ;
cmp.l D,#0 : bgt LAB			->	tst.l D : blt LAB ;

/* change "cmp" into "add" or "sub" (possibly "addq" or "subq") */
cmp.w #-NUM,DSREG : beq LAB		->	add.w #NUM,DSREG : beq LAB ;
cmp.l #-NUM,DSREG : beq LAB		->	add.l #NUM,DSREG : beq LAB ;
cmp.w #-NUM,DSREG : bne LAB		->	add.w #NUM,DSREG : bne LAB ;
cmp.l #-NUM,DSREG : bne LAB		->	add.l #NUM,DSREG : bne LAB ;

cmp.w #NUM,DSREG : beq LAB		->	sub.w #NUM,DSREG : beq LAB ;
cmp.l #NUM,DSREG : beq LAB		->	sub.l #NUM,DSREG : beq LAB ;
cmp.w #NUM,DSREG : bne LAB		->	sub.w #NUM,DSREG : bne LAB ;
cmp.l #NUM,DSREG : bne LAB		->	sub.l #NUM,DSREG : bne LAB ;

/* addq and subq */
lea -1(AREG),AREG			->	sub.l #1,AREG ;
add.w #-NUM,X				->	sub.w #NUM,X ;
add.l #-NUM,X				->	sub.l #NUM,X ;
sub.w #-NUM,X				->	add.w #NUM,X ;
sub.l #-NUM,X				->	add.l #NUM,X ;

/* bit-test instruction */
move.b X,DSREG : and.w #NUM,DSREG : 
 tst.w DSREG : beq LAB 
 { bitno(NUM,BITNO)}			->	btst #BITNO,X ;
move.b X,DSREG : and.l #NUM,DSREG : 
 tst.l DSREG : beq LAB 
 { bitno(NUM,BITNO)}			->	btst #BITNO,X ;

/* skip over jump */
beq L1 : bra L2: labdef L1		->	bne L2 : labdef L1 ;
bge L1 : bra L2: labdef L1		->	blt L2 : labdef L1 ;
bgt L1 : bra L2: labdef L1		->	ble L2 : labdef L1 ;
blt L1 : bra L2: labdef L1		->	bge L2 : labdef L1 ;
ble L1 : bra L2: labdef L1		->	bgt L2 : labdef L1 ;
bne L1 : bra L2: labdef L1		->	beq L2 : labdef L1 ;

move.l A, B : bra LAB : labdef L1 : move.l A, B : labdef LAB
					->	labdef L1 : move.l A, B :
						labdef LAB ;
/* some strength reduction */
mulu.l #NUM,DREG			->	muls.l #NUM,DREG ;
muls.l #NUM,DREG
	{isshift_once(NUM,X,Y)}		->	asl.l #X,DREG :
						asl.l #Y,DREG ;
muls.l #NUM,DREG
	{is_shift_twice(NUM,X,Y)}	->	asl.l #X,DREG :
						move.l DREG,-(sp) :
						asl.l #Y,DREG :
						add.l (sp)+,DREG ;
asl.l #0,DREG				->	;
asl.l #1,DREG				->	add.l DREG,DREG ;


move.l A,-(sp) : move.l B,-(sp) : fmove.d (sp)+,FPREG
	{combines_to_double(A,B)}	->	fmove.d B,FPREG ;
move.l A,-(sp) : move.l B,-(sp) : fmove.d X,FPREG : fmove.d (sp)+,FPREG2
	{combines_to_double(A,B) &&
	 strcmp(FPREG,FPREG2) &&
	 no_part("sp",X) }		->	fmove.d X,FPREG :
						fmove.d B,FPREG2 ;
%%;

/* auxiliary routines: */

int no_side_effects(s)
	register char *s;
{

	for(;;) {
		switch(*s++) {
			case '\0': return TRUE;
			case '-':  if (*s == '(') return FALSE; break;
			case ')':  if (*s == '+') return FALSE; break;
		}
	}
	/* NOTREACHED */
}

int isshift_once(s, c1, c2)
	char *s, *c1, *c2;
{
	long atol();
	register int i = 0;
	long val = atol(s), pow = 1;

	while (i <= 16) {
		if (pow & val) {
			val -= pow;
			if (val == 0) {
				sprintf(c1, "%d", i <= 8 ? i : 8);
				sprintf(c2, "%d", i <= 8 ? 0 : i - 8);
				return 1;
			}
			return 0;
		}
		pow <<= 1;
		i++;
	}
	return 0;
}

int is_shift_twice(s, c1, c2)
	char *s, *c1, *c2;
{
	long atol();
	register int i = 0;
	long val = atol(s), pow = 1;

	while (i <= 8) {
		if (pow & val) {
			val -= pow;
			sprintf(c1, "%d", i);
			break;
		}
		pow <<= 1;
		i++;
	}
	if (i > 8) return 0;
	if (pow > 0 && val) {
		i = 0;
		while (i <= 8) {
			if (pow & val) {
				val -= pow;
				sprintf(c2, "%d", i);
				if (val == 0) return 1;
				break;
			}
			pow <<= 1;
			i++;
		}
	}
	return 0;
}


int is_dreg(s)
	register char *s;
{
	return *s++ == 'd' && *s >= '0' && *s++ <= '7' && *s == '\0';
}

int is_fpreg(s)
	register char *s;
{
	return *s++ == 'f' &&  *s++ == 'p' && 
	       *s >= '0' && *s++ <= '7' && *s == '\0';
}

int is_dsreg(s)
	register char *s;
{
	return *s++ == 'd' && *s >= '0' && *s++ <= '2' && *s == '\0';
}

int is_areg(s)
	register char *s;
{
	return *s++ == 'a' && *s >= '0' && *s++ <= '6' && *s == '\0';
}

int no_part(part,s)
	char *part,*s;
{
	char *tmp1,*tmp2;

	while (*s != '\0') {
		if (*s == *part) {
			for (tmp1=part,tmp2=s;;  tmp1++,tmp2++) {
				if (*tmp1== '\0') return FALSE;
				if (*tmp1 != *tmp2) break;
			}
		}
		s++;
	}
	return TRUE;
}


/* see if register subsumption is allowed for instruction Opc */

int reg_subs_allowed(opc)
	char *opc;
{
	return strcmp(opc,"cmp") != 0 && strcmp(opc,"lea") != 0;
}

int is_number(s)
	register char *s;
{
	while (*s != '\0') {
		if (*s < '0' || *s++ > '9') return FALSE;
	}
	return TRUE;
}

int bitno(s,no)
	char *s,*no;
{
	int n,i;

	n = atoi(s);
	if (n < 1 || n > 128) return FALSE;
	for (i = 0; i < 8 ; i++) {
		if (n == 1) {
			sprintf(no,"%d",i);
			return TRUE;
		}
		n >>= 1;
	}
	return FALSE;
}

int combines_to_double(a,b)
	register char *a,*b;
{
	/*	recognize (_name+4) combined with (_name),
		and (offset+4,...) combined with (offset,...)
	*/
	if (*a++ == '(' && *b++ == '(') {
		if (*a == '-' || *a >= '0' && *a <= '9') {
			int na = atoi(a);
			int nb = atoi(b);

			if (*a == '-') a++;
			if (*b == '-') b++;
			if (na == nb + 4) {
				while (*a >= '0' && *a <= '9') a++;
				while (*b >= '0' && *b <= '9') b++;
				return	!strcmp(a,b) && *a++ == ',' &&
					*a++ == 'a' && *a++ && *a++ == ')' &&
					!*a;
			}
			return FALSE;
		}
		while (*a && *a == *b) {
			a++;
			b++;
		}
		if (*b++ == ')' && ! *b && *a++ == '+' && *a++ == '4' &&
		    *a++ == ')' && !*a)
			return TRUE;
	}
	return FALSE;
}