#include "mcg.h"

static struct symbol* currentproc;
static struct basicblock* current_bb;

static int stackptr;
static struct ir* stack[64];

static struct ir* convert(struct ir* src, int destsize, int opcodebase);
static struct ir* appendir(struct ir* ir);

static void reset_stack(void)
{
    stackptr = 0;
}

static void push(struct ir* ir)
{
    if (stackptr == sizeof(stack)/sizeof(*stack))
        fatal("stack overflow");

    /* If we try to push something which is too small, convert it to a word
     * first. */

    if (ir->size < EM_wordsize)
        ir = convert(ir, EM_wordsize, IR_CIU1);

    stack[stackptr++] = ir;
}

static struct ir* pop(int size)
{
    if (stackptr == 0)
    {
        /* Nothing in our fake stack, so we have to read from the real stack. */

        if (size < EM_wordsize)
            size = EM_wordsize;
        return
            appendir(
                new_ir0(
                    IR_POP, size
                )
            );
    }
    else
    {
        struct ir* ir = stack[--stackptr];

        /* If we try to pop something which is smaller than a word, convert it first. */
        
        if (size < EM_wordsize)
            ir = convert(ir, size, IR_CIU1);

        if (ir->size != size)
            fatal("expected an item on stack of size %d, but got %d\n", size, ir->size);
        return ir;
    }
}

static void print_stack(void)
{
    int i;

    tracef('E', "E: stack:");
    for (i=0; i<stackptr; i++)
    {
        struct ir* ir = stack[i];
        tracef('E', " $%d.%d", ir->id, ir->size);
    }
    tracef('E', "  (top)\n");
}

static struct ir* appendir(struct ir* ir)
{
    int i;

    assert(current_bb != NULL);
    ir->is_sequence = true;
    APPEND(current_bb->irs, ir);

    ir_print('0', ir);
    return ir;
}

static void materialise_stack(void)
{
    int i;

    for (i=0; i<stackptr; i++)
    {
        struct ir* ir = stack[i];
        appendir(
            new_ir1(
                IR_PUSH, ir->size,
                ir
            )
        );
    }

    reset_stack();
}

void tb_filestart(void)
{
}

void tb_fileend(void)
{
}

void tb_regvar(arith offset, int size, int type, int priority)
{
    /* ignored */
}

static struct ir* address_of_external(const char* label, arith offset)
{
    if (offset != 0)
        return
            new_ir2(
                IR_ADD, EM_pointersize,
                new_labelir(label),
                new_wordir(offset)
            );
    else
        return
            new_labelir(label);
}

static struct ir* convert(struct ir* src, int destsize, int opcode)
{
    switch (src->size)
    {
        case 1: opcode += 0; break;
        case 2: opcode += 1; break;
        case 4: opcode += 2; break;
        case 8: opcode += 3; break;
        default:
            fatal("can't convert from things of size %d", src->size);
    }

    return
        new_ir1(
            opcode, destsize,
            src
        );
}

static struct ir* tristate_compare(int size, int opcode)
{
    struct ir* right = pop(size);
    struct ir* left = pop(size);

    return
        new_ir2(
            opcode, EM_wordsize,
            left, right
        );
}

static void simple_convert(int opcode)
{
    struct ir* destsize = pop(EM_wordsize);
    struct ir* srcsize = pop(EM_wordsize);
    struct ir* value;

    assert(srcsize->opcode == IR_CONST);
    assert(destsize->opcode == IR_CONST);

    value = pop(srcsize->u.ivalue);
    push(
        convert(value, destsize->u.ivalue, opcode)
    );
}

static void insn_simple(int opcode)
{
    switch (opcode)
    {
        case op_bra:
        {
            struct ir* dest = pop(EM_pointersize);

            materialise_stack();
            appendir(
                new_ir1(
                    IR_JUMP, 0,
                    dest
                )
            );
            break;
        }
            
        case op_cii: simple_convert(IR_CII1); break;
        case op_ciu: simple_convert(IR_CIU1); break;

        case op_cmp:
            push(
                tristate_compare(EM_pointersize, IR_COMPAREU)
            );
            break;

        case op_cai:
        {
            struct ir* dest = pop(EM_pointersize);

            materialise_stack();
            appendir(
                new_ir1(
                    IR_CALL, 0,
                    dest
                )
            );
            break;
        }

        default:
            fatal("treebuilder: unknown simple instruction '%s'",
                em_mnem[opcode - sp_fmnem]);
    }
}

static void simple_branch2(int opcode, int size,
    struct basicblock* truebb, struct basicblock* falsebb,
    int irop)
{
    struct ir* right = pop(size);
    struct ir* left = pop(size);

    materialise_stack();
    appendir(
        new_ir2(
            IR_CJUMP, 0,
            new_ir2(
                irop,  size,
                left, right
            ),
            new_ir2(
                IR_PAIR, 0,
                new_bbir(truebb),
                new_bbir(falsebb)
            )
        )
    );
}

static void compare0_branch2(int opcode,
    struct basicblock* truebb, struct basicblock* falsebb,
    int irop)
{
    push(
        new_wordir(0)
    );

    simple_branch2(opcode, EM_wordsize, truebb, falsebb, irop);
}

static void insn_bvalue(int opcode, struct basicblock* leftbb, struct basicblock* rightbb)
{
    switch (opcode)
    {
        case op_zeq: compare0_branch2(opcode, leftbb, rightbb, IR_IFEQ); break;
        case op_zlt: compare0_branch2(opcode, leftbb, rightbb, IR_IFLT); break;
        case op_zle: compare0_branch2(opcode, leftbb, rightbb, IR_IFLE); break;

        case op_zne: compare0_branch2(opcode, rightbb, leftbb, IR_IFEQ); break;
        case op_zge: compare0_branch2(opcode, rightbb, leftbb, IR_IFLT); break;
        case op_zgt: compare0_branch2(opcode, rightbb, leftbb, IR_IFLE); break;

        case op_bra:
        {
            materialise_stack();

            appendir(
                new_ir1(
                    IR_JUMP, 0,
                    new_bbir(leftbb)
                )
            );
            break;
        }

        case op_lae:
            push(
                new_bbir(leftbb)
            );
            break;

        default:
            fatal("treebuilder: unknown bvalue instruction '%s'",
                em_mnem[opcode - sp_fmnem]);
    }
}

static void simple_alu1(int opcode, int size, int irop)
{
    struct ir* val = pop(size);

    push(
        new_ir1(
            irop, size,
            val
        )
    );
}

static void simple_alu2(int opcode, int size, int irop)
{
    struct ir* right = pop(size);
    struct ir* left = pop(size);

    push(
        new_ir2(
            irop, size,
            left, right
        )
    );
}

static struct ir* extract_block_refs(struct basicblock* bb)
{
    struct ir* outir = NULL;
    int i;

    for (i=0; i<bb->insns_count; i++)
    {
        struct insn* insn = bb->insns[i];
        assert(insn->opcode == op_bra);
        assert(insn->paramtype == PARAM_BVALUE);

        outir = new_ir2(
            IR_PAIR, 0,
            new_bbir(insn->u.bvalue.left),
            outir
        );
    }

    return outir;
}

static void insn_ivalue(int opcode, arith value)
{
    switch (opcode)
    {
        case op_adi: simple_alu2(opcode, value, IR_ADD); break;
        case op_sbi: simple_alu2(opcode, value, IR_SUB); break;
        case op_mli: simple_alu2(opcode, value, IR_MUL); break;
        case op_dvi: simple_alu2(opcode, value, IR_DIV); break;
        case op_rmi: simple_alu2(opcode, value, IR_MOD); break;
        case op_ngi: simple_alu1(opcode, value, IR_NEG); break;

        case op_and: simple_alu2(opcode, value, IR_AND); break;
        case op_ior: simple_alu2(opcode, value, IR_OR); break;
        case op_xor: simple_alu2(opcode, value, IR_EOR); break;
        case op_com: simple_alu1(opcode, value, IR_NOT); break;

        case op_adf: simple_alu2(opcode, value, IR_ADDF); break;
        case op_sbf: simple_alu2(opcode, value, IR_SUBF); break;
        case op_mlf: simple_alu2(opcode, value, IR_MULF); break;
        case op_dvf: simple_alu2(opcode, value, IR_DIVF); break;
        case op_ngf: simple_alu1(opcode, value, IR_NEGF); break;

        case op_lol:
            push(
                new_ir1(
                    IR_LOAD, EM_wordsize,
                    new_localir(value)
                )
            );
            break;

        case op_stl:
            appendir(
                new_ir2(
                    IR_STORE, EM_wordsize,
                    new_localir(value),
                    pop(EM_wordsize)
                )
            );
            break;

        case op_lal:
            push(
                new_localir(value)
            );
            break;

        case op_loc:
            push(
                new_wordir(value)
            );
            break;

        case op_loi:
            push(
                new_ir1(
                    IR_LOAD, value,
                    pop(EM_pointersize)
                )
            );
            break;

        case op_sti:
        {
            struct ir* ptr = pop(EM_pointersize);
            struct ir* val = pop(value);

            appendir(
                new_ir2(
                    IR_STORE, value,
                    ptr, val
                )
            );
            break;
        }

        case op_cmi:
            push(
                tristate_compare(value, IR_COMPARES)
            );
            break;

        case op_cmu:
            push(
                tristate_compare(value, IR_COMPAREU)
            );
            break;

        case op_ads:
        {
            struct ir* off = pop(value);
            struct ir* ptr = pop(EM_pointersize);

            if (value != EM_pointersize)
                off = convert(off, EM_pointersize, IR_CII1);

            push(
                new_ir2(
                    IR_ADD, EM_pointersize,
                    ptr, off
                )
            );
            break;
        }

        case op_adp:
        {
            struct ir* ptr = pop(EM_pointersize);

            push(
                new_ir2(
                    IR_ADD, EM_pointersize,
                    ptr,
                    new_wordir(value)
                )
            );
            break;
        }

        case op_sbs:
        {
            struct ir* right = pop(EM_pointersize);
            struct ir* left = pop(EM_pointersize);

            struct ir* delta = 
                new_ir2(
                    IR_SUB, EM_pointersize,
                    left, right
                );

            if (value != EM_pointersize)
                delta = convert(delta, value, IR_CII1);

            push(delta);
            break;
        }
            
        case op_dup:
        {
            struct ir* v = pop(value);
            if (!v->is_sequence)
                appendir(v);
            push(v);
            push(v);
            break;
        }

        case op_asp:
        {
            switch (value)
            {
                case 0:
                    break;

                case -1:
                case -2:
                case -4:
                case -8:
                    push(new_anyir(-value));
                    break;

                default:
                    while ((value > 0) && (stackptr > 0))
                    {
                        struct ir* ir = pop(stack[stackptr-1]->size);
                        value -= ir->size;
                    }

                    if (value != 0)
                    {
                        appendir(
                            new_ir1(
                                IR_STACKADJUST, EM_pointersize,
                                new_wordir(value)
                            )
                        );
                    }
                    break;
            }
            break;
        }

        case op_ret:
        {
            if (value > 0)
            {
                struct ir* retval = pop(value);
                materialise_stack();
                appendir(
                    new_ir1(
                        IR_SETRET, value,
                        retval
                    )
                );
            }

            appendir(
                new_ir0(
                    IR_RET, 0
                )
            );
            break;
        }
                    
        case op_lfr:
        {
            push(
                appendir(
                    new_ir0(
                        IR_GETRET, value
                    )
                )
            );
            break;
        }

        case op_csa:
        case op_csb:
        {
            const char* helper = aprintf(".%s%d",
                (opcode == op_csa) ? "csa" : "csb",
                value);
            struct ir* descriptor = pop(EM_pointersize);

            if (descriptor->opcode != IR_LABEL)
                fatal("csa/csb are only supported if they refer "
                    "directly to a descriptor block");

            push(descriptor);
            materialise_stack();
            appendir(
                new_ir2(
                    IR_JUMP, 0,
                    new_labelir(helper),
                    extract_block_refs(bb_get(descriptor->u.lvalue))
                )
            );
            break;
        }

        default:
            fatal("treebuilder: unknown ivalue instruction '%s'",
                em_mnem[opcode - sp_fmnem]);
    }
}

static void insn_lvalue(int opcode, const char* label, arith offset)
{
    switch (opcode)
    {
        case op_lae:
            push(
                address_of_external(label, offset)
            );
            break;

        case op_loe:
            push(
                new_ir1(
                    IR_LOAD, EM_wordsize,
                    address_of_external(label, offset)
                )
            );
            break;

        case op_ste:
            appendir(
                new_ir2(
                    IR_STORE, EM_wordsize,
                    address_of_external(label, offset),
                    pop(EM_wordsize)
                )
            );
            break;

        case op_cal:
            assert(offset == 0);
            materialise_stack();
            appendir(
                new_ir1(
                    IR_CALL, 0,
                    new_labelir(label)
                )
            );
            break;

        case op_bra:
            assert(offset == 0);
            materialise_stack();
            appendir(
                new_ir1(
                    IR_JUMP, 0,
                    new_labelir(label)
                )
            );
            break;
                    
        default:
            fatal("treebuilder: unknown lvalue instruction '%s'",
                em_mnem[opcode - sp_fmnem]);
    }
}

static void generate_tree(struct basicblock* bb)
{
    int i;

    tracef('0', "0: block %s\n", bb->name);

    current_bb = bb;
    reset_stack();

    for (i=0; i<bb->insns_count; i++)
    {
        struct insn* insn = bb->insns[i];
        tracef('E', "E: read %s ", em_mnem[insn->opcode - sp_fmnem]);
        switch (insn->paramtype)
        {
            case PARAM_NONE:
                tracef('E', "\n");
                insn_simple(insn->opcode);
                break;

            case PARAM_IVALUE:
                tracef('E', "value=%d\n", insn->u.ivalue);
                insn_ivalue(insn->opcode, insn->u.ivalue);
                break;

            case PARAM_LVALUE:
                tracef('E', "label=%s offset=%d\n", 
                    insn->u.lvalue.label, insn->u.lvalue.offset);
                insn_lvalue(insn->opcode, insn->u.lvalue.label, insn->u.lvalue.offset);
                break;

            case PARAM_BVALUE:
                tracef('E', "true=%s", insn->u.bvalue.left->name);
                if (insn->u.bvalue.right)
                    tracef('E', " false=%s", insn->u.bvalue.right->name);
                tracef('E', "\n");
                insn_bvalue(insn->opcode, insn->u.bvalue.left, insn->u.bvalue.right);
                break;

            default:
                assert(0);
        }

        if (tracing('E'))
            print_stack();
    }

    assert(stackptr == 0);
}

void tb_procedure(struct procedure* current_proc)
{
    int i;

    for (i=0; i<current_proc->blocks_count; i++)
        generate_tree(current_proc->blocks[i]);

}

/* vim: set sw=4 ts=4 expandtab : */