#include        <stdio.h>
#include        "c.h"
#include        "expr.h"
#include        "gen.h"
#include        "cglbdec.h"

/*
 *68000 C compiler
 *
 *Copyright 1984, 1985, 1986 Matthew Brandt.
 *  all commercial rights reserved.
 *
 *This compiler is intended as an instructive tool for personal use. Any
 *use for profit without the written consent of the author is prohibited.
 *
 *This compiler may be distributed freely for non-commercial use as long
 *as this notice stays intact. Please forward any enhancements or questions
 *to:
 *
 *Matthew Brandt
 *Box 920337
 *Norcross, Ga 30092
 */

struct ocode    *peep_head = 0,
                *peep_tail = 0;

struct amode    *copy_addr(ap)
/*
 *      copy an address mode structure (these things dont last).
 */
struct amode    *ap;
{       struct amode    *newap;
        if( ap == 0 )
                return 0;
        newap = xalloc(sizeof(struct amode));
        newap->mode = ap->mode;
        newap->preg = ap->preg;
        newap->sreg = ap->sreg;
        newap->tempflag = ap->tempflag;
        newap->deep = ap->deep;
        newap->offset = ap->offset;
        return newap;
}

gen_code(op,len,ap1,ap2)
/*
 *      generate a code sequence into the peep list.
 */
enum e_op	op;
int             len;
struct amode    *ap1, *ap2;
{       struct ocode    *new;
        new = xalloc(sizeof(struct ocode));
        new->opcode = op;
        new->length = len;
        new->oper1 = copy_addr(ap1);
        new->oper2 = copy_addr(ap2);
        add_peep(new);
}

add_peep(new)
/*
 *      add the ocoderuction pointed to by new to the peep list.
 */
struct ocode    *new;
{       if( peep_head == 0 )
                {
                peep_head = peep_tail = new;
                new->fwd = 0;
                new->back = 0;
                }
        else
                {
                new->fwd = 0;
                new->back = peep_tail;
                peep_tail->fwd = new;
                peep_tail = new;
                }
}

gen_label(labno)
/*
 *      add a compiler generated label to the peep list.
 */
int	labno;
{       struct ocode    *new;
        new = xalloc(sizeof(struct ocode));
        new->opcode = op_label;
        new->oper1 = (struct amode *)labno;
        add_peep(new);
}

flush_peep()
/*
 *      output all code and labels in the peep list.
 */
{
        if ( Options.Optimize )
           opt3();     /* do the peephole optimizations */
        while( peep_head != 0 )
                {
                if( peep_head->opcode == op_label )
                        put_label((long)(peep_head->oper1));
                else
                        put_ocode(peep_head);
                peep_head = peep_head->fwd;
                }
}

put_ocode(p)
/*
 *      output the instruction passed.
 */
struct ocode    *p;
{       put_code(p->opcode,p->length,p->oper1,p->oper2);
}

peep_move(ip)
/*
 *      peephole optimization for move instructions.
 *      makes quick immediates when possible.
 *      changes move #0,d to clr d.
 *      changes long moves to address registers to short when
 *              possible.
 *      changes move immediate to stack to pea.
 */
struct ocode *ip;
{       struct enode    *ep;
        if( ip->oper1->mode != am_immed )
                return;
        ep = ip->oper1->offset;
        if( ep->nodetype != en_icon )
                return;
        if( ip->oper2->mode == am_areg )
                {
                if( -32768 <= ep->v.i && ep->v.i <= 32768 )
                        ip->length = 2;
                }
        else if( ip->oper2->mode == am_dreg )
                {
                if( -128 <= ep->v.i && ep->v.i <= 127 )
                        {
                        ip->opcode = op_moveq;
                        ip->length = 0;
                        }
                }
        else
                {
                if( ep->v.i == 0 )
                        {
                        ip->opcode = op_clr;
                        ip->oper1 = ip->oper2;
                        ip->oper2 = 0;
                        }
                else if( ip->oper2->mode == am_adec && (int)(ip->oper2)->preg == 7 )
                        {
                        ip->opcode = op_pea;
                        ip->length = 0;
                        ip->oper1->mode = am_direct;
                        ip->oper2 = 0;
                        }
                }
}

int     equal_address(ap1,ap2)
/*
 *      compare two address nodes and return true if they are
 *      equivalent.
 */
struct amode    *ap1, *ap2;
{       if( ap1 == 0 || ap2 == 0 )
                return 0;
        if( ap1->mode != ap2->mode )
                return 0;
        switch( ap1->mode )
                {
                case am_areg:   case am_dreg:
                case am_ainc:   case am_adec:
                        return ap1->preg == ap2->preg;
                }
        return 0;
}

peep_add(ip)
/*
 *      peephole optimization for add instructions.
 *      makes quick immediates out of small constants.
 */
struct ocode    *ip;
{       struct enode    *ep;
        if( ip->oper1->mode != am_immed )
                return;
        ep = ip->oper1->offset;
        if( ip->oper2->mode != am_areg )
                ip->opcode = op_addi;
        else
                {
                if( isshort(ep) )
                        ip->length = 2;
                }
        if( ep->nodetype != en_icon )
                return;
        if( 1 <= ep->v.i && ep->v.i <= 8 )
                ip->opcode = op_addq;
        else if( -8 <= ep->v.i && ep->v.i <= -1 )
                {
                ip->opcode = op_subq;
                ep->v.i = -ep->v.i;
                }
}

peep_sub(ip)
/*
 *      peephole optimization for subtract instructions.
 *      makes quick immediates out of small constants.
 */
struct ocode    *ip;
{       struct enode    *ep;
        if( ip->oper1->mode != am_immed )
                return;
        ep = ip->oper1->offset;
        if( ip->oper2->mode != am_areg )
                ip->opcode = op_subi;
        else
                {
                if( isshort(ep) )
                        ip->length = 2;
                }
        if( ep->nodetype != en_icon )
                return;
        if( 1 <= ep->v.i && ep->v.i <= 8 )
                ip->opcode = op_subq;
        else if( -8 <= ep->v.i && ep->v.i <= -1 )
                {
                ip->opcode = op_addq;
                ep->v.i = -ep->v.i;
                }
}

int     peep_cmp(ip)
/*
 *      peephole optimization for compare instructions.
 *      changes compare #0 to tst and if previous instruction
 *      should have set the condition codes properly delete.
 *      return value is true if instruction was deleted.
 */
struct ocode    *ip;
{       struct ocode    *prev;
        struct enode    *ep;
        if( ip->oper1->mode != am_immed )
                return;
        ep = ip->oper1->offset;
        if( ip->oper2->mode == am_areg )
                {
                if( isshort(ep) )
                        ip->length = 2;
                return;
                }
        ip->opcode = op_cmpi;
        if( ep->nodetype != en_icon || ep->v.i != 0 )
                return;
        ip->oper1 = ip->oper2;
        ip->oper2 = 0;
        ip->opcode = op_tst;
        prev = ip->back;
        if( prev == 0 )
                return;
        if( (((prev->opcode == op_move || prev->opcode == op_moveq) &&
                equal_address(prev->oper1,ip->oper1)) &&
                prev->oper2->mode != am_areg) ||
                (prev->opcode != op_label &&
                equal_address(prev->oper2,ip->oper1)) )
                {
                prev->fwd = ip->fwd;
                if( prev->fwd != 0 )
                        prev->fwd->back = prev;
                }
}

peep_muldiv(ip,op)
/*
 *      changes multiplies and divides by convienient values
 *      to shift operations. op should be either op_asl or
 *      op_asr (for divide).
 */
struct ocode    *ip;
enum e_op	op;
{       int     shcnt;
        if( ip->oper1->mode != am_immed )
                return;
        if( ip->oper1->offset->nodetype != en_icon )
                return;
        switch( shcnt = (int)(ip->oper1->offset->v.i) )
	  {
            case 2: shcnt = 1; break;
            case 4: shcnt = 2; break;
            case 8: shcnt = 3; break;
            case 16: shcnt = 4; break;
            case 32: shcnt = 5; break;
            case 64: shcnt = 6; break;
            case 128: shcnt = 7; break;
            case 256: shcnt = 8; break;
            case 512: shcnt = 9; break;
            case 1024: shcnt = 10; break;
            case 2048: shcnt = 11; break;
            case 4096: shcnt = 12; break;
            case 8192: shcnt = 13; break;
            case 16384: shcnt = 14; break;
            default: return;
          }
        ip->oper1->offset->v.i = shcnt;
        ip->opcode = op;
        ip->length = 4;
}

peep_uctran(ip)
/*
 *      peephole optimization for unconditional transfers.
 *      deletes instructions which have no path.
 *      applies to bra, jmp, and rts instructions.
 */
struct ocode    *ip;
{       while( ip->fwd != 0 && ip->fwd->opcode != op_label )
                {
                ip->fwd = ip->fwd->fwd;
                if( ip->fwd != 0 )
                        ip->fwd->back = ip;
                }
}

opt3()
/*
 *      peephole optimizer. This routine calls the instruction
 *      specific optimization routines above for each instruction
 *      in the peep list.
 */
{       struct ocode    *ip;
        ip = peep_head;
        while( ip != 0 )
                {
                switch( ip->opcode )
                        {
                        case op_move:
                                peep_move(ip);
                                break;
                        case op_add:
                                peep_add(ip);
                                break;
                        case op_sub:
                                peep_sub(ip);
                                break;
                        case op_cmp:
                                peep_cmp(ip);
                                break;
                        case op_muls:
                                peep_muldiv(ip,op_asl);
                                break;
                        case op_bra:
                        case op_jmp:
                        case op_rts:
                                peep_uctran(ip);
                        }
                ip = ip->fwd;
                }
}

