/*
 * C compiler
 * ==========
 *
 * Copyright 1989, 1990, 1991 Christoph van Wuellen.
 * Credits to Matthew Brandt.
 * All commercial rights reserved.
 *
 * This compiler may be redistributed as long there is no
 * commercial interest. The compiler must not be redistributed
 * without its full sources. This notice must stay intact.
 *
 * History:
 *
 * 1989   starting an 68000 C compiler, starting with material
 *        originally by M. Brandt
 * 1990   68000 C compiler further bug fixes
 *        started i386 port (December)
 * 1991   i386 port finished (January)
 *        further corrections in the front end and in the 68000
 *        code generator.
 *        The next port will be a SPARC port
 */

/*
 * Register allocation (for the expression evaluation)
 * This modules handles the management of scratch registers.
 * It keeps track of the allocated registers and of the stack
 */

#include "config.h"

#ifdef MC680X0

#include "chdr.h"
#include "expr.h"
#include "cglbdec.h"
#include "proto.h"
#include "gen68k.h"
#include "outproto.h"

#if defined(__STDC__) || defined(__cplusplus)
#define P_(s) s
#else
#define P_(s) ()
#endif

static	void	g_push	P_((REG, DEEP));
static	void	g_pop	P_((REG, DEEP));

#undef P_

/*
**	This data structure is used to keep track of registers which
**	have been pushed onto the stack.
*/
static struct {
    REG		reg;
    DEEP	depth;
}               reg_stack[(int)MAX_REG_STACK + 1];
static	DEEP	stack_depth;

/*
**	This data structure is used to keep track of register which
**	have been allocated.
*/
static struct {
    REG		reg;
    BOOL	pushed;
}               reg_alloc[(int)MAX_REG_STACK + 1];
static	DEEP	alloc_depth;

/*
**	Define the registers which can be used to pass parameters.
*/
static REG	parameter_registers[] = {
		    D0, D1, D2, A0, A1
};
static REGLIST parameter_list = {
		    (int)(sizeof (parameter_registers) / sizeof (REG)),
		    &parameter_registers[0]
};

/*
**	Define the registers which must be saved by a function if
**	they are used.
**	If the register is used to return a value then it needn't be
**	saved.
**	If the register is used to pass a parameter then it needn't be
**	saved.
*/
static	REG	saved_registers[] = {
		    D3, D4, D5, D6, D7,
		    A2, A3, A4, A5, A6, A7
#ifdef FLOAT_IEEE
		    ,FP2 ,FP3, FP4, FP5, FP6, FP7
#endif /* FLOAT_IEEE */
};
static	REGLIST	saved_list = {
		    (int) (sizeof (saved_registers) / sizeof (REG)),
		    &saved_registers[0]
};

/*
**	Define the registers which are used to return the results
**	from a function call.
*/
static	REG	result_registers[] = {
		    D0, D1, D2
};
static	REGLIST	result_list = {
		    (int) (sizeof (result_registers) / sizeof (REG)),
		    &result_registers[0]
};

static REGUSAGE	rusage = {
		    &parameter_list,
		    &saved_list,
		    &result_list
};
REGUSAGE *	reg_usage = &rusage;

REGTYPE regtype[] = {
	D_REG | T_REG,	/* D0 */
	D_REG | T_REG,	/* D1 */
	D_REG | T_REG,	/* D2 */
	D_REG,		/* D3 */
	D_REG,		/* D4 */
	D_REG,		/* D5 */
	D_REG,		/* D6 */
	D_REG,		/* D7 */
	A_REG | T_REG,	/* A0 */
	A_REG | T_REG,	/* A1 */
	A_REG,		/* A2 */
	A_REG,		/* A3 */
	A_REG,		/* A4 */
	A_REG,		/* A5 */
	A_REG,		/* A6 */
	A_REG,		/* A7 */
#ifdef FLOAT_IEEE
	F_REG | T_REG,	/* FP0 */
	F_REG | T_REG,	/* FP1 */
	F_REG | T_REG,	/* FP2 */
	F_REG,		/* FP3 */
	F_REG,		/* FP4 */
	F_REG,		/* FP5 */
	F_REG,		/* FP6 */
	F_REG		/* FP7 */
#endif /* FLOAT_IEEE */
};

static	DEEP	reg_in_use[NUM_REGS];
static  ADDRESS	apush = {am_adec, STACKPTR, (REG)0, EMPTY, NIL_EXPR};
static  ADDRESS	apop =  {am_ainc, STACKPTR, (REG)0, EMPTY, NIL_EXPR};

static	REG	next_data;	/* next temporary data register */
static	REG	next_addr;	/* next temporary address register */
#ifdef FLOAT_IEEE
static	REG	next_float;	/* next temporary floating point register */
#endif /* FLOAT_IEEE */


/*
**	This routine generates code to push a register onto the stack.
*/
static void
g_push P2(REG, reg, DEEP, depth)
{
    ADDRESS   *ap;
    sync_stack();
    ap = mk_reg(reg);
    switch (reg) {
      case D0:
      case D1:
      case D2:
      case D3:
      case D4:
      case D5:
      case D6:
      case D7:
      case A0:
      case A1:
      case A2:
      case A3:
      case A4:
      case A5:
      case A6:
      case A7:
	g_move(IL4, ap, &apush);
	break;
#ifdef FLOAT_IEEE
      case FP0:
      case FP1:
      case FP2:
      case FP3:
      case FP4:
      case FP5:
      case FP6:
      case FP7:
	g_fcode(op_fmove, IL12, ap, &apush);
#endif /* FLOAT_IEEE */
	break;
      default:
	CANNOT_REACH_HERE();
    }
    reg_stack[stack_depth].reg = reg;
    reg_stack[stack_depth].depth = depth;

    if (reg_alloc[depth].pushed)
	FATAL ((__FILE__,"g_push","reg %d already pushed", (int)reg));
    reg_alloc[depth].pushed = TRUE;

    /* check on stack overflow */
    if (++stack_depth > MAX_REG_STACK)
	FATAL ((__FILE__,"g_push","register stack overflow"));
}


/*
**	Generate code to pop a register from the stack.
*/
static void
g_pop P2(REG, reg, DEEP, depth)
{
    ADDRESS   *ap;

    /* check on stack underflow */
    if (stack_depth-- == EMPTY)
	FATAL ((__FILE__,"g_pop","register stack empty"));

    /* check if the desired register really is on stack */
    if (reg_stack[stack_depth].depth != depth)
	FATAL ((__FILE__,"g_pop","register order (%d,%d)", (int)depth, (int)reg_stack[stack_depth].depth));

    /* check if the register which is restored is really void */
    if (reg_in_use[reg] != UNUSED)
	FATAL ((__FILE__,"g_pop","register %d in use", (int)reg));

    reg_in_use[reg] = depth;
    sync_stack();
    ap = mk_reg(reg);
    switch (reg) {
      case D0:
      case D1:
      case D2:
      case D3:
      case D4:
      case D5:
      case D6:
      case D7:
      case A0:
      case A1:
      case A2:
      case A3:
      case A4:
      case A5:
      case A6:
      case A7:
	g_move(IL4, &apop, ap);
	break;
#ifdef FLOAT_IEEE
      case FP0:
      case FP1:
      case FP2:
      case FP3:
      case FP4:
      case FP5:
      case FP6:
      case FP7:
	g_fcode(op_fmove, IL12, &apop, ap);
#endif /* FLOAT_IEEE */
	break;
      default:
	CANNOT_REACH_HERE();
     }

    /* clear the push_flag */
    reg_alloc[depth].pushed = FALSE;
}


/*
**	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.
**
**	This is also a good place to free all 'pseudo' registers
**	in the stack frame by setting act_scratch to zero.
*/
void
initstack P0(void)
{
    REG	reg;
    next_data = D0;
    next_addr = A0;
#ifdef FLOAT_IEEE
    next_float = FP0;
    for (reg = D0; reg <= FP7; reg++)
#else
    for (reg = D0; reg <= A7; reg++)
#endif /* FLOAT_IEEE */
	reg_in_use[reg] = UNUSED;
    stack_depth = EMPTY;
    alloc_depth = EMPTY;
    act_scratch = 0;
}


/*
**	This routines checks if all allocated registers were freed.
*/
void
checkstack P0(void)
{
    REG reg;
    if (next_data != D0)
	FATAL ((__FILE__,"checkstack","D0 not next temporary"));
    if (next_addr != A0)
	FATAL ((__FILE__,"checkstack","A0 not next temporary"));
#ifdef FLOAT_IEEE
    if (next_float != FP0)
	FATAL ((__FILE__,"checkstack","FP0 not next temporary"));
#endif
    for (reg=D0; reg <= MAX_REG; reg++) {
	if (!(regtype[reg] & T_REG))
	    continue;
        if (reg_in_use[reg] != UNUSED)
            FATAL ((__FILE__,"checkstack","register %d still in use", (int)reg));
    }
    if (stack_depth != EMPTY)
	FATAL ((__FILE__,"checkstack","register stack not empty"));
    if (alloc_depth != EMPTY)
	FATAL ((__FILE__,"checkstack","allocated register stack not empty"));
}


/*
**	Validate will make sure that if a register within an address
**	mode has been pushed onto the stack that it is popped back
**	at this time.
*/
void
validate P1(const ADDRESS*, ap)
{
    REG		reg;
    switch (ap->mode) {
      case am_xreg:
	reg = ap->u.xreg;
	if (is_temporary_data_register(reg) && reg_alloc[ap->deep].pushed) {
	    g_pop(reg, (DEEP)((int)ap->deep+2));
	}
	/*FALLTHRU*/
      case am_mreg:
	reg = ap->sreg;
	if (is_temporary_data_register(reg) && reg_alloc[ap->deep].pushed) {
	    g_pop(reg, (DEEP)((int)ap->deep+1));
	}
	/*FALLTHRU*/
      case am_dreg:
	reg = ap->preg;
	if (is_temporary_data_register(reg) && reg_alloc[ap->deep].pushed) {
	    g_pop(reg, ap->deep);
	}
	break;
      case am_indx2:
      case am_indx4:
	reg = ap->sreg;
	if (is_temporary_data_register(reg) && reg_alloc[ap->deep].pushed) {
	    g_pop(reg, ap->deep);
	}
	goto common;
      case am_indx3:
	reg = ap->sreg;
	if (is_temporary_address_register(reg) && reg_alloc[ap->deep].pushed) {
	    g_pop(reg, ap->deep);
	}
	goto common;
      case am_areg:
      case am_ind:
      case am_indx:
      case am_ainc:
      case am_adec:
common:
	reg = ap->preg;
	if (is_temporary_address_register(reg) && reg_alloc[ap->deep].pushed) {
	    g_pop(reg, ap->deep);
	}
	break;
#ifdef FLOAT_IEEE
      case am_freg:
	reg = ap->preg;
	if (is_temporary_float_register(reg) && reg_alloc[ap->deep].pushed) {
	    g_pop(reg, ap->deep);
	}
	break;
#endif /* FLOAT_IEEE */
      default:
	break;
    }
}

/*
**	Return the next register of type 'kind'
*/
static REG
next_reg P2(REG, reg, REGTYPE, kind)
{
    for(;;) {
	reg = (reg == FP7) ? D0 : (REG)((int)reg+1);
	if (((regtype[reg] & T_REG)) && (regtype[reg] & kind))
	    return reg;
    }
}

/*
**	Return the previous register of type 'kind'
*/
static REG
prev_reg P2(REG, reg, REGTYPE, kind)
{
    for(;;) {
	reg = (reg == D0) ? FP7 : (REG)((int)reg-1);
	if (((regtype[reg] & T_REG)) && (regtype[reg] & kind))
	    return reg;
    }
}

/*
**	Allocate a temporary register.    Returns the next register
**	that will be allocated.
*/
static REG
allocate_register P2(REG, reg, REGTYPE, kind)
{
    if (reg_in_use[reg] != UNUSED) {
	/*
	**	The next available register is already in use.
	**	It must be pushed.
	*/
	g_push(reg, reg_in_use[reg]);
    }

    reg_in_use[reg] = alloc_depth;
    reg_alloc[alloc_depth].reg = reg;
    reg_alloc[alloc_depth].pushed = FALSE;

    if (alloc_depth++ == MAX_REG_STACK)
	FATAL ((__FILE__,"allocate_register","register stack overflow"));
    return next_reg(reg, kind);
}

/*
**	Allocate a temporary data register and return
**	it's addressing mode.
*/
ADDRESS   *
data_register P0(void)
{
    ADDRESS *ap = mk_reg(next_data);
    next_data = allocate_register(next_data, D_REG);
    ap->deep = reg_in_use[ap->preg];
    return ap;
}


/*
**	Allocate 2 temporary data registers and return
**	it's addressing mode.
*/
ADDRESS *
mdata_register P0(void)
{
    REG	     reg1;
    REG	     reg2;
    ADDRESS *ap;
    reg1 = next_data;
    next_data = allocate_register(next_data, D_REG);
    reg2 = next_data;
    next_data = allocate_register(next_data, D_REG);
    ap = mk_mreg (reg1, reg2);
    ap->deep = reg_in_use[reg1];
    return ap;
}

/*
**	Allocate 3 temporary data registers and return
**	it's addressing mode.
*/
ADDRESS *
xdata_register P0(void)
{
    REG	     reg1;
    REG	     reg2;
    REG	     reg3;
    ADDRESS *ap;
    reg1 = next_data;
    next_data = allocate_register(next_data, D_REG);
    reg2 = next_data;
    next_data = allocate_register(next_data, D_REG);
    reg3 = next_data;
    next_data = allocate_register(next_data, D_REG);
    ap = mk_xreg (reg1, reg2, reg3);
    ap->deep = reg_in_use[reg1];
    return ap;
}


/*
**	Allocate a temporary addr register and return it's addressing mode.
*/
ADDRESS   *
address_register P0(void)
{
    ADDRESS *ap = mk_reg(next_addr);
    next_addr = allocate_register(next_addr, A_REG);
    ap->deep = reg_in_use[ap->preg];
    return ap;
}

#ifdef FLOAT_IEEE
/*
**	Allocate a temporary floationg point register and return it's
**	addressing mode.
*/
ADDRESS *
float_register P0(void)
{
    ADDRESS *ap = mk_reg(next_float);
    next_float = allocate_register(next_float, F_REG);
    ap->deep = reg_in_use[ap->preg];
    return ap;
}
#endif /* FLOAT_IEEE */

/*
**	Returns TRUE if a data register is available at ,,no cost'' (no push).
**	Used to determine e.g. whether cmp.w #0,An or move.l An,Dm is better
*/
BOOL
is_free_data P0(void)
{
    return (reg_in_use[next_data] == UNUSED);
}


/*
**	returns TRUE if an address register is available at
**	,,no cost'' (no push).
*/
BOOL
is_free_addr P0(void)
{
    return (reg_in_use[next_addr] == UNUSED);
}


/*
**	Allocates a data or addressing register (whichever is free).
**	Otherwise allocates the first register which matches flags.
*/
ADDRESS	*
temp_reg P1(FLAGS, flags)
{
    if (is_free_data() && (flags & F_DREG))
	return data_register();
    if (is_free_addr() && (flags & F_AREG))
	return address_register();
    if (flags & F_DREG)
	return data_register();
    if (flags & F_AREG)
	return address_register();
#ifdef FLOAT_IEEE
    if (flags & F_FREG)
	return float_register();
#endif /* FLOAT_IEEE */
    return NIL_ADDRESS;
}


/*
**	Deallocate the specified register.
*/
static void
deallocate_register P1(REG, reg)
{
    DEEP	depth;
    if (!is_temporary_register(reg))
	return;
    if (is_data_register(reg)) {
	next_data = prev_reg(next_data, D_REG);
    } else if (is_address_register(reg)) {
	next_addr = prev_reg(next_addr, A_REG);
#ifdef FLOAT_IEEE
    } else if (is_float_register(reg)) {
	next_float = prev_reg(next_float, F_REG);
#endif /* FLOAT_IEEE */
    }
    depth = reg_in_use[reg];
    reg_in_use[reg] = UNUSED;

    /* we should only free the most recently allocated register */
    if (alloc_depth-- == EMPTY)
	FATAL ((__FILE__,"deallocate_register","register stack empty"));

    if (alloc_depth != depth)
	FATAL ((__FILE__,"deallocate_register","register stack order"));

    /* the just freed register should not be on stack */
    if (reg_alloc[depth].pushed)
	FATAL ((__FILE__,"deallocate_register","register pushed"));
}


/*
**	Release any temporary registers used in an addressing mode.
*/
void
freeop P1(const ADDRESS*, ap)
{
    DEEP	depth;
    REG		reg;
    if (ap == NIL_ADDRESS)
	/* This can happen freeing a NOVALUE result */
	return;
    switch (ap->mode) {
      case am_xreg:
	deallocate_register(ap->u.xreg);
	/*FALLTHRU*/
      case am_mreg:
      case am_indx2:
      case am_indx3:
      case am_indx4:
	deallocate_register(ap->sreg);
	/*FALLTHRU*/
      case am_dreg:
      case am_areg:
      case am_ainc:
      case am_adec:
      case am_ind:
      case am_indx:
#ifdef FLOAT_IEEE
      case am_freg:
#endif /* FLOAT_IEEE */
	reg = ap->preg;
	break;
      default:
	return;
    }

    if (!is_temporary_register(reg))
	return;

    depth = reg_in_use[reg];
    deallocate_register(reg);

    /* some consistency checks */
    if (depth != ap->deep)
	FATAL ((__FILE__,"freeop","1"));
}


/*
**	Push any used temporary registers.
**
**	This is necessary across function calls
**	The reason for this hacking is actually that temp_inv()
**	should dump the registers in the correct order,
**
**	The least recently allocate register first.
**	The most recently allocated register last.
*/
void
temp_inv P0(void)
{
    DEEP	deep;

    for (deep = EMPTY; deep < alloc_depth; deep++)
	if (!reg_alloc[deep].pushed) {
	    g_push(reg_alloc[deep].reg, deep);
	    /* mark the register void */
	    reg_in_use[reg_alloc[deep].reg] = UNUSED;
	}
}


/*
**	Converts a list of registers into a register mask
*/
REGMASK
reglist_to_mask P1(const REGLIST *, rp)
{
    REGMASK	mask = (REGMASK)0;
    int		num;
    for (num = 0; num < rp->number; num++) {
	mask |= (REGMASK)(1 << (int)rp->reg[num]);
    }
    return mask;
}
#endif /* MC680X0 */
