/*
 * 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
 *
 * 1995   Ivo Oesch, Started to build a codegenerator for the 
 *        signalprocessor TMS320C30 (December)
 */

/*
 * 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 TMS320C30

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

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

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

#undef P_

static REG      next_data;      /* next temporary data register */
static REG      next_addr;      /* next temporary address register */
static REG      next_ireg;      /* next temporary index register */

static DEEP     reg_in_use[MAX_REG + 1];

/*
**      This data structure is used to keep track of registers which
**      have been pushed onto the stack.
*/
static struct {
    REG         reg;
    AMODE       mode;
    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;
    AMODE       mode;
    BOOL        pushed;
}               reg_alloc[(int)MAX_REG_STACK + 1];
static  DEEP    alloc_depth;


/*
 * this routine generates code to push a register onto the stack
 */
static void
g_push P3(REG, reg, AMODE, amode, DEEP, depth)
{
    ADDRESS   *ap;
    sync_stack();
    ap = mk_reg(reg);
    if (amode == am_freg)        
      g_code(op_pushf, OP_FLOAT, ap, NIL_ADDRESS);
    else                
      g_code(op_push, OP_INT, ap, NIL_ADDRESS);

    reg_stack[stack_depth].mode = amode;
    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);
    if (reg_stack[stack_depth].mode == am_freg)
        g_code(op_popf, OP_INT, ap, NIL_ADDRESS);
    else
        g_code(op_pop, OP_INT, ap, NIL_ADDRESS);

    /* 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 = REG_R0;
    next_addr = REG_AR0;
    next_ireg = REG_IR0;       
    for (reg = REG_R0; reg <= MAX_REG; reg++)
        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;
    for (reg=REG_R0; reg<= MAX_REG; reg++) {
        if (reg_in_use[reg] != UNUSED)
            FATAL((__FILE__,"checkstack","register %d still in use", (int)reg));
    }
    if (next_data != REG_R0)
        FATAL((__FILE__,"checkstack","R0 not next temporary"));
    if (next_addr != REG_AR0)
        FATAL((__FILE__,"checkstack","AR0 not next temporary"));
    if (next_ireg != REG_IR0)
        FATAL((__FILE__,"checkstack","IR0 not next temporary"));
    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_dreg:
        reg = ap->preg;
        if (reg <= MAX_DATA && reg_alloc[ap->deep].pushed) {
            g_pop(reg, ap->deep);
        }
        break;
      case am_freg:
        reg = ap->preg;
        if (reg <= MAX_DATA && reg_alloc[ap->deep].pushed) {
            g_pop(reg, ap->deep);
        }
        break;
      case am_ireg:
        reg = ap->preg;
        if (reg <= REG_IR1 && reg_alloc[ap->deep].pushed) {
            g_pop(reg, ap->deep);
        }
        break;    
      case am_indx2:
        reg = ap->sreg;
        if (reg <= REG_IR1 && reg_alloc[ap->deep].pushed) {
            g_pop(reg, ap->deep);
        }
        /*FALLTHRU*/
      case am_areg:
      case am_ind:
      case am_indx:
      case am_ainc:
      case am_adec:
        reg = ap->preg;
        if (reg <= MAX_ADDR && reg_alloc[ap->deep].pushed) {
            g_pop(reg, ap->deep);
        }
        break;
    }
}

/*
 * allocate a temporary data register and return it's addressing mode.
 */
ADDRESS   *
data_register P1(FLAGS, flags)
{
    ADDRESS     *ap;
    DEEP        oldnbr;    
    AMODE       mode = (flags == F_FREG) ? am_freg : am_dreg;;
        
    if (reg_in_use[next_data] != UNUSED)  {
        /*
         * The next available register is already in use. it must be pushed
         */
        oldnbr = reg_in_use[next_data];                
        g_push(next_data, reg_alloc[oldnbr].mode, oldnbr);
    }
    reg_in_use[next_data] = alloc_depth;
    ap = mk_reg(next_data);
    ap->mode = mode;
    ap->deep = alloc_depth;
    reg_alloc[alloc_depth].reg = next_data;
    reg_alloc[alloc_depth].mode = mode;
    reg_alloc[alloc_depth].pushed = FALSE;
    if (next_data++ == MAX_DATA)
        next_data = REG_R0;          /* wrap around */
    if (alloc_depth++ == MAX_REG_STACK)
        FATAL((__FILE__,"data_register",""));
    return ap;
}

/*
 * allocate a temporary addr register and return it's addressing mode.
 */
ADDRESS   *
address_register P0(void)
{
    ADDRESS   *ap;
    if (reg_in_use[next_addr] != UNUSED) {
        /*
         * The next available register is already in use. it must be pushed
         */
        g_push(next_addr, am_areg, reg_in_use[next_addr]);
    }
    reg_in_use[next_addr] = alloc_depth;
    ap = mk_reg(next_addr);
    ap->deep = alloc_depth;
    reg_alloc[alloc_depth].reg = next_addr;
    reg_alloc[alloc_depth].mode = am_areg;
    reg_alloc[alloc_depth].pushed = FALSE;
    if (next_addr++ == MAX_ADDR)
        next_addr = REG_AR0;          /* wrap around */
    if (alloc_depth++ == MAX_REG_STACK)
        FATAL((__FILE__,"address_register",""));
    return ap;
}

/*
 * allocate a temporary ireg register and return it's addressing mode.
 */
ADDRESS   *
index_register P0(void)
{
    ADDRESS   *ap;
    if (reg_in_use[next_ireg] != UNUSED) {
        /*
         * The next available register is already in use. it must be pushed
         */
        g_push(next_ireg, am_ireg, reg_in_use[next_ireg]);
    }
    reg_in_use[next_ireg] = alloc_depth;
    ap = mk_reg(next_ireg);
    ap->deep = alloc_depth;
    reg_alloc[alloc_depth].reg = next_ireg;
    reg_alloc[alloc_depth].mode = am_ireg;
    reg_alloc[alloc_depth].pushed = FALSE;
    if (next_ireg++ == REG_IR1)
        next_ireg = REG_IR0;          /* wrap around */
    if (alloc_depth++ == MAX_REG_STACK)
        FATAL((__FILE__,"index_register",""));
    return ap;
}

/*
 * 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);
}

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

ADDRESS *
temporary_register P1(FLAGS, flags)
{
    if (is_free_data() && (flags & F_DREG))
        return data_register(F_DREG);
    if (is_free_addr() && (flags & F_AREG))
        return address_register();
    if (is_free_ireg() && (flags & F_IREG))
        return index_register();
    if (flags & F_DREG)
        return data_register(F_DREG);
    if (flags & F_AREG)
        return address_register();
    if (flags & F_IREG)
        return index_register();
    if (flags & F_FREG)
        return data_register(F_FREG);
    FATAL((__FILE__,"temporary_register","no free registers"));
    /* NOTREACHED */    
    return NIL_ADDRESS;
}

/*
 * 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_freg:              
      case am_dreg:
        reg = ap->preg;
        if (reg <= MAX_DATA) {
            if (next_data-- == REG_R0)
                next_data = MAX_DATA;
            depth = reg_in_use[reg];
            reg_in_use[reg] = UNUSED;
            break;
        }
        return;
                  
      case am_ireg:
        reg = ap->preg;
        if (reg <= REG_IR1) {
            if (next_ireg-- == REG_IR0)
                next_ireg   = REG_IR1;
            depth = reg_in_use[reg];
            reg_in_use[reg] = UNUSED;
            break;
        }
        return;
                          
      case am_indx2:
        reg = ap->sreg;
        if (reg <= REG_IR1) {
            if (next_ireg-- == REG_IR0)
                next_ireg   = REG_IR1;
            depth = reg_in_use[reg];
            reg_in_use[reg] = UNUSED;
            reg = ap->preg;
            if (reg <= MAX_ADDR) {
                if (next_addr-- == REG_AR0)
                    next_addr = MAX_ADDR;
                depth = reg_in_use[reg];
                reg_in_use[reg] = UNUSED;
                break;
            }
            break;
        }
        return;
      case am_areg:
      case am_ind:
      case am_indx:
      case am_ainc:
      case am_adec:
        reg = ap->preg;
        if (reg <= MAX_ADDR) {
            if (next_addr-- == REG_AR0)
                next_addr = MAX_ADDR;
            depth = reg_in_use[reg];
            reg_in_use[reg] = UNUSED;
            break;
        }
        return;
      default:
        return;
    }
    /* some consistency checks */
    if (depth != ap->deep)
        FATAL((__FILE__,"freeop","1"));
    /* we should only free the most recently allocated register */
    if (alloc_depth-- == EMPTY)
        FATAL((__FILE__,"freeop","2"));
    if (alloc_depth != depth)
        FATAL((__FILE__,"freeop","3"));
    /* the just freed register should not be on stack */
    if (reg_alloc[depth].pushed)
        FATAL((__FILE__,"freeop","4"));
}

#if 0
void 
save_blockrepeat P0(void)
{
}
        
void 
restore_blockrepeat P0(void)
{
}       
#endif


/*
 * 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 depth;

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