/* 
   Copyright 1994-2003 Free Software Foundation, Inc.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
   02111-1307, USA.  

   You may contact the author at:

   mailto::camille@bluegrass.net

   or by snail mail at:

   David Lindauer
   850 Washburn Ave Apt 99
   Louisville, KY 40222
*/
#include        <stdio.h>
#include				<string.h>
#include        "lists.h"
#include        "expr.h"
#include        "c.h"
#include        "gen386.h"
#include				"diag.h"

/*
 *      this module handles the allocation and de-allocation of
 *      temporary registers. when a temporary register is allocated
 *      the stack depth is saved in the field deep of the address
 *      mode structure. when validate is called on an address mode
 *      structure the stack is popped until the register is restored
 *      to it's pre-push value.
 */
extern int cf_freedata,cf_freeaddress,cf_freefloat;

	AMODE push[1],pop[1];
int             max_data;			/* Max available */
int regs[3];
int pushcount;
static unsigned char pushedregs[40];
static char readytopop[40];
int lastreg1,lastreg2;

#define EAXEDX 240
#define FUNCHASH 241

void regini(void)
{
	initstack();
}
void set_func_mode(int mode)
{
   if (mode)
      pushedregs[pushcount++] = FUNCHASH ;
   else if (pushedregs[pushcount-1] == FUNCHASH)
      pushcount-- ;
}
void gen_push(int reg, int rmode, int flag)
/*
 *      this routine generates code to push a register onto the stack
 */
{       AMODE    *ap1;
        ap1 = xalloc(sizeof(AMODE));
        ap1->preg = reg;
        ap1->mode = rmode;
				ap1->length = 4;
				if (rmode == am_freg) {
			FLOAT;
				}
				else {
        	OCODE *new = xalloc(sizeof(OCODE));
        	new->opcode = op_push;
        	new->oper1 = ap1;
        	add_peep(new);
				}
}

void gen_pop(int reg, int rmode, int flag)
/*
 *      generate code to pop the primary register in ap from the
 *      stack.
 */
{       AMODE    *ap1;
        ap1 = xalloc(sizeof(AMODE));
        ap1->preg = reg;
        ap1->mode = rmode;
				ap1->length = 4;
				if (rmode == am_freg) {
			FLOAT;
				}
				else {
        	OCODE *new = xalloc(sizeof(OCODE));
        	new->opcode = op_pop;
        	new->oper1 = ap1;
        	add_peep(new);
				}
}
void pushregs(unsigned mask)
{
	int i;
	int umask = 0x08000;
	for (i=0; i < 4; i++) {
		if (umask & mask)
			gen_push(i,am_dreg,1);
		umask >>=1;
	}
	umask = 0x080;
	for (i=4; i < 8; i++) {
		if (umask & mask)
			gen_push(i,am_dreg,1);
		umask >>=1;
	}
}
/* This is ONLY called from the return.  Calling from any other place
 * will leave the stack depth unpredictable... */
void popregs(unsigned mask)
{
	int i;
	int umask = 0x800;
	for (i=7; i >= 4; i--) {
		if (umask & mask) {
			gen_pop(i,am_dreg,1);
		}
		umask >>=1;
	}
	umask = 0x8;
	for (i=3; i >= 0; i--) {
		if (umask & mask) {
			gen_pop(i,am_dreg,1);
		}
		umask >>=1;
	}
}
int dregbase;
void initstack(void)
/*
 *      this routine should be called before each expression is
 *      evaluated to make sure the stack is balanced and all of
 *      the registers are marked free.
 */
{
         if (pushcount) {
            if (pushedregs[pushcount-1] == FUNCHASH)
               return ;
				gen_code(op_add,makedreg(ESP),make_immed(pushcount * 4));
         }
			pushcount = 0;
      max_data = cf_freedata-1;
			regs[0]=regs[1]=regs[2]=0;
}
int next_dreg(void)
{
	int reg;
	if (!regs[0])
		return regs[0]*3;
	else
		if (!regs[1])
			return regs[1]*3+1;
		else
			if (!regs[2])
				return regs[2]*3+2;
			else 
				if (lastreg1 == 0)
					if (lastreg2 == 1)
						return regs[2]*3+2;
					else
						return regs[1]*3+1;
				else if (lastreg1 == 1)
					if (lastreg2 == 0)
						return regs[2]*3+2;
					else
						return regs[0]*3;
				else if (lastreg1 == 2)
					if (lastreg2 == 0)
						return regs[1]*3+1;
					else
						return regs[0]*3;						
}
AMODE    *temp_data(void)
/*
 *      allocate a temporary data register and return it's
 *      addressing mode.
 */
{       AMODE    *ap = xalloc(sizeof(AMODE));
				int reg = next_dreg();
				int rp3 = reg %3;
				if (regs[rp3]++) {
					gen_push(rp3,am_dreg,0);
					pushedregs[pushcount] = rp3;
					readytopop[pushcount++] = 0;
				}
				lastreg2 = lastreg1;
				lastreg1 = rp3;
    	  ap->mode = am_dreg;
			  ap->preg = rp3;
				ap->tempflag = TRUE;
				ap->length = 4;
        return ap;
}

void tempaxdx(void)
{
   if (regs[2] || regs[0]) {
      gen_push(EDX,am_dreg,0) ;
      gen_push(EAX,am_dreg,0) ;
      pushedregs[pushcount] = EAXEDX ;
      readytopop[pushcount++] = 0 ;
   }
   regs[2]++ ;
   regs[0]++ ;
}
AMODE *tempcx(void)
{
   AMODE *ap = xalloc(sizeof(AMODE)) ;
   if (regs[1]++) {
      gen_push(ECX,am_dreg,0) ;
      pushedregs[pushcount] = ECX ;
      readytopop[pushcount++] = 0 ;
   }
    	  ap->mode = am_dreg;
           ap->preg = ECX;
				ap->tempflag = TRUE;
				ap->length = 4;
        return ap;
}
/*
 *
 */
void freedata(int dreg)
{
	if (dreg < cf_freedata && dreg >=0) {
		if (regs[dreg]) {
			int i;
			for (i=pushcount-1; i >= 0; i--)
				if (pushedregs[i] == dreg)
					break;
			if (i >= 0) {
				readytopop[i] = 1;
				while (readytopop[pushcount-1]) {
					pushcount--;
					gen_pop(pushedregs[pushcount],am_dreg,0);
				}
			}
			regs[dreg]--;
			
		}
	}
}
void freeaxdx(void)
{
   if (pushedregs[pushcount-1] == EAXEDX) {
      pushcount -- ;
      gen_pop(EAX,am_dreg,0 );
      gen_pop(EDX,am_dreg,0) ;
      regs[EAX]-- ;
      regs[EDX]-- ;
   } else {
      freedata(EDX) ;
      freedata(EAX);
   }
   while (pushcount && readytopop[pushcount-1]) {
      pushcount--;
      gen_pop(pushedregs[pushcount],am_dreg,0);
   }
}
void freeop(AMODE *ap)
/*
 *      release any temporary registers used in an addressing mode.
 */
{
        if( ap->mode == am_dreg)
					freedata(ap->preg);
		 		else if (ap->mode == am_indisp)		
					freedata(ap->preg);
		 		else if (ap->mode == am_indispscale) {
					freedata(ap->preg);
					freedata(ap->sreg);
            } else if (ap->mode == am_axdx) {
               freeaxdx() ;
            }
}