/* 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.
 */

/*
 * Single-instruction peephole optimizations and the overall driver routine.
 */

#include "top.h"


void
peep(bp)
register BLOCK	*bp;
{
	bool	peep1(), peep2(), peep3();
	extern	BLOCK	*fhead;
	register bool	changed;

	peep1(bp);

	/*
	 * Loop until no more changes are made. After each change, do
	 * live/dead analysis or the data gets old. In each loop, make
	 * at most one change.
	 */
	do {
		changed = peep3(bp);

		if (!changed)
			changed = peep2(bp);

		if (!changed)
			changed = peep1(bp);

		rhealth(fhead, FALSE);

	} while (changed);
}

/*
 * ipeep1(ip) - check for changes to the instruction 'ip'
 */
static	bool
ipeep1(bp, ip)
register BLOCK	*bp;
register INST	*ip;
{
	/*
	 * clr.l  Dn			=> moveq.l  Dn
	 */
	if (ip->opcode == CLR && ip->src.amode == REG &&
	    ISD(ip->src.areg) && (ip->flags & LENL)) {
		ip->opcode = MOVEQ;
		ip->dst = ip->src;	/* we'll have two operands now */
		ip->src.amode = IMM;
		ip->src.disp = 0;
		DBG(printf("%d ", __LINE__))
		return TRUE;
	}

	/*
	 * move.*  #n,Dn		=> moveq.l  #n,Dn
	 *
	 * moveq is always a long operation, but as long as the immediate
	 * value is appropriate, we don't care what the original length
	 * was. Clearing upper bytes won't matter.
	 */
	if (ip->opcode == MOVE && ip->src.amode == IMM && ISD(ip->dst.areg) &&
	    D8OK(ip->src.disp)) {
	    	ip->opcode = MOVEQ;
	    	ip->flags = LENL;
		DBG(printf("%d ", __LINE__))
	    	return TRUE;
	}

	/*
	 * add.x  #n, X 		=> addq.x  #n, X
	 *
	 * where 1 <= n <= 8
	 */
	if (ip->opcode == ADD && ip->src.amode == IMM &&
	    ip->src.disp >= 1 && ip->src.disp <= 8) {
	    	ip->opcode = ADDQ;
		DBG(printf("%d ", __LINE__))
	    	return TRUE;
	}

	/*
	 * sub.x  #n, X 		=> subq.x  #n, X
	 *
	 * where 1 <= n <= 8
	 */
	if (ip->opcode == SUB && ip->src.amode == IMM &&
	    ip->src.disp >= 1 && ip->src.disp <= 8) {
	    	ip->opcode = SUBQ;
		DBG(printf("%d ", __LINE__))
	    	return TRUE;
	}

	/*
	 * movem.x  Reg,-(sp)		=> move.x Reg,-(sp)
	 */
	if (ip->opcode == MOVEM && ip->src.amode == REG &&
	    ip->dst.areg == SP && ip->dst.amode == (REGI|DEC)) {
	    	ip->opcode = MOVE;
		DBG(printf("%d ", __LINE__))
	    	return TRUE;
	}

	/*
	 * movem.x  (sp)+,Reg		=> move.x (sp)+,Reg
	 */
	if (ip->opcode == MOVEM && ip->dst.amode == REG &&
	    ip->src.amode == (REGI|INC) && ip->src.areg == SP) {
	    	ip->opcode = MOVE;
		DBG(printf("%d ", __LINE__))
	    	return TRUE;
	}

	/*
	 *	add[q]	#?, Rn
	 *
	 * Remove instruction if Rn is dead. This is most often used
	 * to eliminate the fixup of SP following a function call when
	 * we're just about to return, since the "unlk" clobbers SP
	 * anyway.
	 */
	if ((ip->opcode == ADDQ || ip->opcode == ADD) && ip->src.amode == IMM &&
	     ip->dst.amode == REG) {

		if ((ip->live & RM(ip->dst.areg)) == 0) {
		     	delinst(bp, ip);
			DBG(printf("%d ", __LINE__))
		     	return TRUE;
		}
	}

	/*
	 *	move.x	X, X
	 *
	 *	Delete as long as X isn't INC or DEC
	 */
	if ((ip->opcode == MOVE) && opeq(&ip->src, &ip->dst) &&
	    ((ip->src.amode & (INC|DEC)) == 0)) {

		delinst(bp, ip);
		DBG(printf("%d ", __LINE__))
		return TRUE;
	}

	/*
	 *	move.x	Rm, Rn
	 *
	 *	Delete if Rn is dead.
	 */
	if (ip->opcode == MOVE &&
	     ip->src.amode == REG && ip->dst.amode == REG) {

		if ((ip->live & RM(ip->dst.areg)) == 0) {
		     	delinst(bp, ip);
			DBG(printf("%d ", __LINE__))
		     	return TRUE;
		}
	}


	/*
	 *	cmp.x	#0, X		=>	tst.x	X
	 *	beq/bne				beq/bne
	 *
	 *	Where X is not An
	 */
	if (bp->last == ip && (bp->bcode == BEQ || bp->bcode == BNE) &&
	    ip->opcode == CMP &&
	    ((ip->dst.amode != REG) || !ISA(ip->dst.areg))) {

		if (ip->src.amode == IMM && ip->src.disp == 0) {
			ip->opcode = TST;
			ip->src = ip->dst;
			ip->dst.amode = NONE;
			DBG(printf("%d ", __LINE__))
			return TRUE;
		}
	}

	/*
	 * add.x  #n, Am 		=> lea  n(Am), Am
	 *
	 * where 'n' is a valid displacement
	 */
	if (ip->opcode == ADD && ip->src.amode == IMM && ip->dst.amode == REG &&
	    ISA(ip->dst.areg) && DOK(ip->src.disp)) {
	    	ip->opcode = LEA;
	    	ip->flags = 0;
	    	ip->src.amode = REGID;
	    	ip->src.areg = ip->dst.areg;
		DBG(printf("%d ", __LINE__))
	    	return TRUE;
	}

	return FALSE;
}

/*
 * peep1(bp) - peephole optimizations with a window size of 1
 */
static	bool
peep1(bp)
register BLOCK	*bp;
{
	register INST	*ip;
	register bool	changed = FALSE;
	register bool	bchange;

	DBG(printf("p1: "))
	for (; bp != NULL ;bp = bp->next) {
		bchange = FALSE;
		for (ip = bp->first; ip != NULL ;ip = ip->next) {
			if (ipeep1(bp, ip)) {
				s_peep1++;
				changed = TRUE;
				bchange = TRUE;
			}
		}
		if (bchange)
			bprep(bp);
	}
	DBG(printf("\n"); fflush(stdout))
	return changed;
}
