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

#include "config.h"

#ifdef TMS320C30

/*#define peepinfo(String)        VOIDCAST printf(String)*/ 
#define peepinfo(String)

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

static CODE *peep_head = 0;
static CODE *next_ip;
static int changes;

#if 0
static OPCODE revcond[] = { op_bne, op_beq, op_bge, op_bgt, op_ble, op_blt,
                               op_bls, op_blo, op_bhs, op_bhi };
#endif

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

static CODE *code               P_((OPCODE, ILEN, ADDRESS *, ADDRESS *, ADDRESS *, ADDRESS *, ADDRESS *, ADDRESS *));
static void add_peep            P_((CODE *));
static void opt3                P_((void));
static void peep_delete         P_((CODE *));
static void peep_line           P_((CODE *));
static CODE *find_label         P_((LABEL));
static void peep_label          P_((CODE *));
static BOOL uses_label          P_((ADDRESS *, LABEL));

#undef P_

/*
 *      The next two #define statements are to make the
 *      code in the branch optimisation clearer.  Tests
 *      have shown that the in-line size cost is about
 *      the same as making them functions so we keep
 *      them in-line for speed.
 */

#define same_instruction(ip1,ip2) \
    ((ip1 != 0) && (ip2 != 0) && \
    (ip1->opcode == ip2->opcode) && \
    (ip1->length == ip2->length) && \
    (is_equal_oper (ip1->oper1, ip2->oper1)) && \
    (is_equal_oper (ip1->oper2, ip2->oper2)))


/* backup over any sequence of labels to previous instruction */
#define previous_instruction(ip) \
    do { \
        ip = ip->back; \
    } while (ip != 0 && ip->opcode == op_label)

/* backup over any sequence of lines to previous instruction */
#define previous_ignore_line(ip) \
    do { \
        ip = ip->back; \
    } while (ip != 0 && ip->opcode == op_line)


#define branch(ip)      (  (ip->opcode >= op_bu && ip->opcode <= op_bnn)\
                         ||(ip->opcode == op_br))

#define anybranch(ip)   (  (ip->opcode >= op_bu && ip->opcode <= op_bnn)\
                         ||(ip->opcode == op_br) \
                         ||(ip->opcode >= op_bud && ip->opcode <= op_bnnd)\
                         ||(ip->opcode == op_brd))

static ADDRESS   *
mk_branchcomment P0(void)
{
    ADDRESS   *ap;
    EXPR        *ep;
    ep = mk_node(en_str, NIL_EXPR, NIL_EXPR, tp_void);
    ep->v.str = (CHAR *)";Branch occurs here";
    ap = (ADDRESS *) xalloc((size_t) sizeof(ADDRESS));
    ap->mode = am_str;
    ap->u.offset = ep;
    return ap;
}

/*
 * find the end of a block of code.
 */
static CODE *
block_end P1(CODE*, ip)
{
    int count = 0;
    while (   ip != NIL_CODE && ip->opcode != op_br
           && ip->opcode != op_bu
           && ip->opcode != op_retsu) {
        if (count == BRANCH_COUNT)
            return NIL_CODE;
        if (branch(ip))
            count++;
        ip = ip->fwd;
    }
    return ip;
}


/*
 * find the node which contains the label 'lab'
 */
static CODE *
find_label P1(LABEL, lab)
{
    register CODE *ip;
    for (ip = peep_head; ip != NIL_CODE; ip = ip->fwd) {
        if (ip->opcode == op_label && ip->oper1->u.offset->v.l == lab)
            return ip;
    }
    /* we should have found it */
    return NIL_CODE;
}

/*
 * counts the number of times that a label node is referenced
 */
static int
label_references P1(CODE *, ip)
{
    CODE        *target;
    struct swtab*sw;
    LABEL       i;
    LABEL       lab = ip->oper1->u.offset->v.l;
    int         count = 0;
    for (target = peep_head; target != NIL_CODE; target = target->fwd) {
        if ((target != ip) &&
            (uses_label(target->oper1, lab) ||
             uses_label(target->oper2, lab)))
            count++;
    }
    for (sw=swtables; sw != 0; sw = sw->next) {
        for (i=0; i < sw->numlabs; i++) {
            if (sw->labels[i] == lab)
                count++;
        }
    }
    if (ip->back != NIL_CODE && ip->back->opcode == op_label)
        count++;
    if (ip->fwd != NIL_CODE && ip->fwd->opcode == op_label)
        count++;
    return count;
}



/*
 * compare two address nodes and return true if they are equivalent.
 */
BOOL
is_equal_address P2(ADDRESS *, ap1, ADDRESS *, ap2)
{
    if (ap1 == 0 || ap2 == 0)
        return FALSE;
    if (ap1->mode != ap2->mode)
        return FALSE;
    switch (ap1->mode) {
      case am_areg:
      case am_dreg:
      case am_freg:
      case am_ireg:
      case am_sreg:
      case am_ind:
        return ap1->preg == ap2->preg;
      case am_indx:
        return ap1->preg == ap2->preg &&
               ap1->u.offset->nodetype == en_icon &&
               ap2->u.offset->nodetype == en_icon &&
               ap1->u.offset->v.i == ap2->u.offset->v.i;
      case am_indx2:
        return
            ap1->preg == ap2->preg &&
            ap1->sreg == ap2->sreg &&
            ap1->u.offset->nodetype == en_icon &&
            ap2->u.offset->nodetype == en_icon &&
            ap1->u.offset->v.i == ap2->u.offset->v.i;
    }
    return FALSE;
}


static CODE *
code P8(OPCODE, op, ILEN, len, ADDRESS *, ap1, ADDRESS *, ap2, ADDRESS *, ap3, ADDRESS *, ap21, ADDRESS *, ap22, ADDRESS *, ap23)
{
    CODE   *newcode;
    newcode = (CODE *) xalloc((int) sizeof(CODE));
    newcode->opcode = op;
    newcode->length = len;
    newcode->oper1 = ap1;
    newcode->oper2 = ap2;
    newcode->oper3 = ap3;
    newcode->oper21 = ap21;
    newcode->oper22 = ap22;
    newcode->oper23 = ap23;
    return newcode;
}

/*
 * generate a code sequence into the peep list.
 */
void
g_code P4(OPCODE, op, int, len, ADDRESS *, ap1, ADDRESS *, ap2)
{
    add_peep(code(op, (ILEN)len, ap1, ap2, NIL_ADDRESS, NIL_ADDRESS, NIL_ADDRESS, NIL_ADDRESS));
}

/*
 * generate a code sequence into the peep list.
 */
void
g_code_parallel P8(OPCODE, op, int, len, ADDRESS *, ap1, ADDRESS *, ap2, ADDRESS *, ap3, ADDRESS *, ap21, ADDRESS *, ap22, ADDRESS *, ap23)
{
    add_peep(code(op, (ILEN)len, ap1, ap2, ap3, ap21, ap22, ap23));
}

/*
 * add the instruction pointed to by new to the peep list.
 */
static void
add_peep P1(CODE *, newcode)
{
    static CODE *peep_tail;
    if (peep_head == 0) {
        peep_head = peep_tail = newcode;
        newcode->fwd = 0;
        newcode->back = 0;
    } else {
        newcode->fwd = 0;
        newcode->back = peep_tail;
        peep_tail->fwd = newcode;
        peep_tail = newcode;
    }
}

/*
 * output all code and labels in the peep list.
 */
void
flush_peep P0(void)
{
    register CODE *ip;
    struct swtab  *sw;
    EXPR          *ep2;
    int           i;
    opt3();                     /* do the peephole optimizations */
    for (ip = peep_head; ip != 0; ip = ip->fwd) {
        if (ip->opcode == op_label)
            put_label(ip->oper1->u.offset->v.l);
        else
            put_code(ip);
    }
    peep_head = 0;
    for(sw=swtables; sw; sw=sw->next) {
        put_kseg(alignment(tp_pointer));
        put_label(sw->tablab);
        ep2 = mk_lcon((LABEL)0);
        /* generate the switch jump table as a series of 4-byte addresses */
        for(i=0; i<sw->numlabs; i++) {
            ep2->v.l = sw->labels[i];
            put_pointer(ep2);
        }
    }
    swtables = NULL;
}

/*
 * delete an instruction referenced by ip
 */
static void
peep_delete P1(CODE *, ip)
{
    if (ip == NIL_CODE) {
        FATAL ((__FILE__,"peep_delete",""));
    }
    if (ip->back == NIL_CODE) {
        peep_head = ip->fwd;
        if (ip->fwd)
            ip->fwd->back = NIL_CODE;
        next_ip = ip->fwd;
    } else {
        if ((ip->back->fwd = ip->fwd) != NIL_CODE) {
            ip->fwd->back = ip->back;
        }
        next_ip = ip->back;
    }
    changes++;
}


/* delete branches to the following statement */
static void
peep_br P1(CODE *, ip)
{
    CODE   *p = ip->fwd;
    LABEL   label;
    CODE   *target;

    if (ip->oper1->mode != am_immed)
        return;

    label = ip->oper1->u.offset->v.l;
    /* delete branches to the following statement */
    while (p != 0 && (  (p->opcode == op_label) 
                      ||(p->opcode == op_line))) {
        if ((p->opcode == op_label)&&(p->oper1->u.offset->v.l == label)) {
            peep_delete(ip);
            peepinfo (" Peep eliminated br to next line\n");      
            return;
        }
        p = p->fwd;
    }
  /*  if (!optimize_option)
   *    return;
   */
    target = find_label(label);
    /* we should have found it */
    if (target == NIL_CODE)
        FATAL ((__FILE__,"peep_bra","target == 0"));

    /* Space optimisation:
     * if the code before the target of the branch is itself a branch
     * then we can move the destination block of code to eliminate the branch
     */
    p = target->back;
    if (p != NULL && (  (p->opcode == op_br)
                     || (p->opcode == op_bu)
                     || (p->opcode == op_retsu))) {
        p = block_end(target);
        if (p != NULL && p != ip) {
            if (ip->fwd)
                ip->fwd->back = p;
            if (p->fwd)
                p->fwd->back = target->back;
            target->back->fwd = p->fwd;
            p->fwd = ip->fwd;
            target->back = ip;
            ip->fwd = target;
            peepinfo (" Peep eliminated br trough blockmove\n");      
            peep_delete(ip);
            return;
        }
    }
}

static void
peep_brdelayed P1(CODE *, ip)
{
    CODE   *p;
    CODE   *newcode;
    int    count;

    /* Speed optimisation:
     * if the code before the branch contains no labels and no branches or 
     * calls we use delayed branches (the next three instructions after 
     * the branch are executed before the branch really occurs, therfore 
     * the next three instructiuns cannot be any kind of branches, labels, 
     * asm, calls and jumps
     * if the branch cannot be moved for three locations, we must fill 
     * in nops
     * this replacement should only be done in the last pass of the optimizer
     * e.g when all unneeded labels are eliminated and all codemoves are done                      
     */
    for (p = ip->back, count = 0; (p!= NULL)&&(count < 3); p = p->back) {    
	if (  (p->opcode == op_asm)
	    ||(p->opcode == op_label)
	    ||(p->opcode == op_call)
	    ||(p->opcode == op_callu)
	    ||(anybranch(p))
	    ||(p->opcode == op_retsu)) {
	    break;                           
	} else {
	    if (p->opcode != op_line) {
		count++;
	    }                         
	}                         
    }
    /* do replace only if at least (opt_delayed_branches) number
     * of valid instructions will follow in the next 3 instructions
     * after the branch
     * (or not more as (3-opt_delayed_branches) nops must be included
     * this allows some balancing between speed and codesize
     */
    if (count >= opt_delayed_branches) {
	newcode = code (op_asm, OP_INT, mk_branchcomment(), NIL_ADDRESS, NIL_ADDRESS, NIL_ADDRESS, NIL_ADDRESS, NIL_ADDRESS);
	newcode->back = ip;
	newcode->fwd  = ip->fwd;
	if (ip->fwd != 0) {
	    ip->fwd->back = newcode;
	    ip->fwd = newcode;
	} else {
	    ip->fwd = NULL;
	    /*peep_tail = newcode;*/
	} 
	while(count < 3) {
	    newcode = code (op_nop, OP_INT, NIL_ADDRESS, NIL_ADDRESS, NIL_ADDRESS, NIL_ADDRESS, NIL_ADDRESS, NIL_ADDRESS);
	    newcode->back = ip;
	    newcode->fwd  = ip->fwd;
	    ip->fwd->back = newcode;
	    ip->fwd = newcode;
	    count++;
	}
      
	peepinfo(" Peep added delayed branch\n");
	peep_delete(ip);
	if (p == NULL)  {
	    peep_head->back = ip;
	    ip->fwd = peep_head;
	    ip->back = NULL;
	    peep_head = ip;
	} else {
	    p->fwd->back = ip;
	    ip->fwd = p->fwd;
	    ip->back = p;
	    p->fwd  = ip;
	}
	ip->opcode = op_brd;
    }
    return;
}


/*
 * peephole optimization for ldi instructions.
 */
static void
peep_ldi P1(CODE *, ip)
{
    CODE   *ip2;

    /*
     * remove ldi rx,rx (they may set the flags but flags are ignored after ldi
     *        
     */
    if (is_equal_address(ip->oper1, ip->oper2)) { 
        peep_delete(ip);
        peepinfo (" Peep eliminated ldi rx,rx\n");        
        return;
    }
         
    /*
     * go back to previous valid instruction
     */
    ip2 = ip; 
    do {
	ip2 = ip2->back;
    } while ((ip2 != 0)&&(ip2->opcode == op_line));              
                  
    /* eliminate redundant move with registers on sequence:
     *          ldi    Xn, Xm
     *          ldi    Xm, Xn
     */
    if ((ip2 != 0) && (ip2->opcode == op_ldi) &&
        is_equal_address (ip->oper1, ip2->oper2) &&
        is_equal_address (ip->oper2, ip2->oper1)) {
        peep_delete(ip);
        peepinfo (" Peep eliminated ldi rx,ry ldi ry,rx\n");      
        return;
    }
    /* eliminate redundant move with registers on sequence:
     *          sti    Xn, Xm
     *          ldi    Xm, Xn
     */
    if ((ip2 != 0) && (ip2->opcode == op_sti) &&
        is_equal_address (ip->oper1, ip2->oper2) &&
        is_equal_address (ip->oper2, ip2->oper1)) {
        peep_delete(ip);
        peepinfo (" Peep eliminated sti rx,ry ldi ry,rx\n");      
        return;
    }
}
         
/*
 * peephole optimization for ldi instructions.
 */
static void
peep_ldf P1(CODE *, ip)
{
    CODE   *ip2;

    /*
     * remove ldf rx,rx (they may set the flags but flags are ignored after ldi
     *        
     */
    if (is_equal_address(ip->oper1, ip->oper2)) {
        peep_delete(ip);
        peepinfo (" Peep eliminated ldf rx,rx\n");        
        return;
    }
    /*
     * go back to previous valid instruction
     */
    ip2 = ip; 
    do {
	ip2 = ip2->back;
    } while ((ip2 != 0)&&(ip2->opcode == op_line));              
                  
    /* eliminate redundant move with registers on sequence:
     *          ldf    Xn, Xm
     *          ldf    Xm, Xn
     */
    if ((ip2 != 0) && (ip2->opcode == op_ldf) &&
        is_equal_address (ip->oper1, ip2->oper2) &&
        is_equal_address (ip->oper2, ip2->oper1)) {
        peep_delete(ip);
        peepinfo (" Peep eliminated ldf rx,ry ldf ry,rx\n");      
        return;
    }
    /* eliminate redundant move with registers on sequence:
     *          stf    Xn, Xm
     *          ldf    Xm, Xn
     */
    if ((ip2 != 0) && (ip2->opcode == op_stf) &&
        is_equal_address (ip->oper1, ip2->oper2) &&
        is_equal_address (ip->oper2, ip2->oper1)) {
        peep_delete(ip);
        peepinfo (" Peep eliminated stf rx,ry ldf ry,rx\n");      
        return;
    }
}        

/*
 * peephole optimization for sti instructions.
 */
static void
peep_sti P1(CODE *, ip)
{
    CODE   *ip2;

    /* eliminate redundant move with registers on sequence:
     *          ldi    Xn, Xm
     *          sti    Xm, Xn
     */
    /*
     * go back to previous valid instruction
     */
    ip2 = ip; 
    do {
	ip2 = ip2->back;
    } while ((ip2 != 0)&&(ip2->opcode == op_line));              
                  
          
    if ((ip2 != 0) && (ip2->opcode == op_ldi) &&
        is_equal_address (ip->oper1, ip2->oper2) &&
        is_equal_address (ip->oper2, ip2->oper1)) {
        peep_delete(ip);
        peepinfo (" Peep eliminated ldi rx,ry sti ry,rx\n");      
        return;
    }
}
        

/*
 * peephole optimization for stf instructions.
 */
static void
peep_stf P1(CODE *, ip)
{
    CODE   *ip2;

    /* eliminate redundant move with registers on sequence:
     *          ldf    Xn, Xm
     *          stf    Xm, Xn
     */
    /*
     * go back to previous valid instruction
     */
    ip2 = ip; 
    do {
	ip2 = ip2->back;
    } while ((ip2 != 0)&&(ip2->opcode == op_line));              
                  
    if ((ip2 != 0) && (ip2->opcode == op_ldf) &&
        is_equal_address (ip->oper1, ip2->oper2) &&
        is_equal_address (ip->oper2, ip2->oper1)) {
        peep_delete(ip);
        peepinfo (" Peep eliminated ldf rx,ry stf ry,rx\n");      
        return;
    }
}
    
/*
 * if a label is followed by a branch to another label, the
 * branch statement can be deleted when the label is moved
 */
static void
peep_label P1(CODE *, ip)
{
    CODE   *next;
#if 0
    CODE   *prev, *target;
    struct swtab   *sw;
    LABEL           i, lab, label;
#endif

    if ((next = ip->fwd) == NIL_CODE)
        return;

    /*if (!optimize_option)
     *  return;
     */
#if 0
    lab = ip->oper1->u.offset->v.l;
#endif
    switch (next->opcode) {
#if 0            
      case op_label:
        /* if a label is followed by a label then common them up */
        label = next->oper1->u.offset->v.l;
        for (target = peep_head; target != NIL_CODE; target = target->fwd) {
            if (uses_label ( target->oper1, label))
                target->oper1->u.offset->v.l = lab;
            if (uses_label ( target->oper2, label))
                target->oper2->u.offset->v.l = lab;
        }
        for (sw=swtables; sw != 0; sw = sw->next) {
            if (sw->beglab == label)
                sw->beglab = lab;
            for (i=0; i < sw->numlabs; i++) {
                if (sw->labels[i] == label)
                    sw->labels[i] = lab;
            }
        }
        peep_delete(next);
        break;

      case op_bra:
        prev = ip->back;
        /*
         * To make this fast, assume that the label number is really
         * next->oper1->u.offset->v.l
         */
        label = next->oper1->u.offset->v.l;
        if (label == lab)
            return;
        target = find_label(label);
        if (target == NIL_CODE) {
           message(MSG_PEEPLABEL);
           return;
        }
        /* move label */
        if (target->fwd == ip)
            return;
        peep_delete(ip);
        ip->fwd = target->fwd;
        ip->back = target;
        target->fwd = ip;
        if (ip->fwd != NIL_CODE)
            ip->fwd->back = ip;
        /* possibly remove branches */
        /* in fact, prev is always != 0 if peep_delete has succeeded */
        if (prev != NIL_CODE) {
            if (prev->opcode == op_bra || prev->opcode == op_jmp
                || prev->opcode == op_rts)
                peep_uctran(prev);
        }
        break;
#endif            
      default:
        /* check that there are still references to this label */
        if (label_references(ip) == 0) {
            peepinfo (" Peep eliminated unreferenced label\n");      
            peep_delete(ip);
        }                       
        break;
    }
}

/*
 * Returns false if the <ea> does is not a label or else isn't equal to label
 */
static BOOL
uses_label P2(ADDRESS *, ap, LABEL, label)
{
    return (ap != NIL_ADDRESS &&
            ap->mode == am_immed &&
            ap->u.offset->nodetype == en_labcon &&
            ap->u.offset->v.l == label);
}


/* delete multiple debugging line statements */
static void
peep_line P1(CODE *, ip)
{
    CODE   *ip2;
    if (ip->fwd == NULL)
        return;
    switch (ip->fwd->opcode) {
      case op_line:
        if (ip->oper1->u.offset->v.i==ip->fwd->oper1->u.offset->v.i)        
	    peep_delete(ip);
        break;
      case op_label:
        /* move the line number to after the label */
        ip2 = ip->fwd;
        if (ip->back)
            ip->back->fwd = ip2;
        if (ip2->fwd)
            ip->fwd->back = ip;
        ip2->back = ip->back;
        ip->fwd = ip2->fwd;
        ip2->fwd = ip;
        ip->back = ip2;
        break;
    }
}

static void
opt3 P0(void)
/*
 * peephole optimizer. This routine calls the instruction specific
 * optimization routines above for each instruction in the peep list.
 */
{
    CODE   *ip;
    if (! opt_option) 
        return;
    do {
        changes = 0;
        next_ip = peep_head;
        while (next_ip != 0) {
            ip = next_ip;
            next_ip = ip->fwd;
            switch (ip->opcode) {
              case op_ldi:
                peep_ldi(ip);
                break;
              case op_ldf:
                peep_ldf(ip);
                break;
              case op_sti:
                peep_sti(ip);
                break;
              case op_stf:
                peep_stf(ip);
                break;
              case op_br:
              case op_bu:
                peep_br(ip);
                break;
              case op_label:
                peep_label(ip);
                break;
              case op_line:
                peep_line(ip);
                break;
            }
        }
    } while (changes);

    /*
     * In a last pass we try to use delayed branches whenever it is possible
     * this must be done in the last Pass, sinc the three statements following
     * a delayed branch will be executed till the real branch is done
     * and thos instructions must not be either of Label, branches, calls,
     * returns and Repeats 
     */
    if (opt_delayed_branches>0) {
	next_ip = peep_head;
	while (next_ip != 0) {
	    ip = next_ip;
	    next_ip = ip->fwd;
	    switch (ip->opcode) {
	      case op_br:
		peep_brdelayed(ip);
		break;
	    }
	}
    } 
}
#endif /* TMS320C30 */
