// Mike Maxim
// Code generator

package edu.cmu.cs.l3.x86;

import edu.cmu.cs.l3.general.*;
import edu.cmu.cs.l3.tree.*;
import edu.cmu.cs.l3.assem.*;
import java.util.*;
import edu.cmu.cs.l3.frame.*;
import edu.cmu.cs.l3.translate.*;
import edu.cmu.cs.l3.codegen.*;
import edu.cmu.cs.l3.temp.*;

public class X86CodeGenerator implements CodeGenerator, Visitor {

    public X86CodeGenerator(boolean regspill) {
	m_instrs = new ArrayList();
        m_regspill = regspill;
    }

    private static final int STRFRAGS=0,GLBFRAGS=1,CODEFRAGS=2;

    public List codegen(List fragments) {

        int i,stage;
        List glbinstrs = new ArrayList();
        List actuals = new ArrayList();

        glbinstrs.add(new OperInstruction("/* Compiled by L3-java (Mike Maxim) */\n\n", null, null));
        for (stage = STRFRAGS; stage <= CODEFRAGS; stage++) {

            List stgfrags = getTypedFragments(fragments,stage);
            if (stage == STRFRAGS) {
                glbinstrs.add(new OperInstruction(".section rodata\n",null,null));
            }
            else if (stage == GLBFRAGS) {
                glbinstrs.add(new OperInstruction(".data\n",null,null));
            }
            else if (stage == CODEFRAGS) {
                glbinstrs.add(new OperInstruction(".text\n\t.align 4\n",null,null));
            }
            for (i = 0; i < stgfrags.size(); i++) {
                Fragment frag = (Fragment) stgfrags.get(i);
                m_curframe = frag.getFrame();
                m_instrs.clear();
                if (frag instanceof FragmentCode) {
                    ((FragmentCode)frag).getCode().visit(this);
                    List entryinstrs = m_curframe.generateEntry(((FragmentCode)frag).getName());
                    List exitinstrs = m_curframe.generateExit();

                    entryinstrs.addAll(m_instrs);
                    entryinstrs.addAll(exitinstrs);
                    frag.setAssemCode(entryinstrs);
                    actuals.add(frag);
                }
                else if (frag instanceof FragmentString) {
                    FragmentString cfrag = (FragmentString) frag;
                    glbinstrs.add(new LabelInstruction(cfrag.getLabel().toString() + ":\n",cfrag.getLabel()));
                    glbinstrs.add(new OperInstruction("\t.ascii \"" + cfrag.getString() + "\\0\"\n",null,null));
                }
                else if (frag instanceof FragmentGlobal) {
                    FragmentGlobal cfrag = (FragmentGlobal) frag;
                    if (!cfrag.isPointer()) {
                        glbinstrs.add(
                        new LabelInstruction(cfrag.getTemp().getTemp().toString() + ":\n", null));
                        glbinstrs.add(
                            new OperInstruction("\t.long\t0\n", null, null));
                    }
                    else {
                        glbinstrs.add(
                        new LabelInstruction(cfrag.getTemp().getTemp().getGlobalName()+ ":\n", null));
                        glbinstrs.add(
                            new OperInstruction("\t.long\t0\n", null, null));
                        glbinstrs.add(
                            new OperInstruction("\t.long\t0\n", null, null));
                        glbinstrs.add(
                            new OperInstruction("\t.long\t0\n", null, null));
                    }
                }
            }

        }

        FragmentCode glbfrag = new FragmentCode(m_curframe,"____Global");
        glbfrag.setAssemCode(glbinstrs);
        actuals.add(0,glbfrag);

        return actuals;
    }

    private List getTypedFragments(List fragments, int type) {

        List tyfrags = new ArrayList();
        int i;

        for (i = 0; i < fragments.size(); i++) {
            Fragment cfrag = (Fragment) fragments.get(i);
            switch (type) {
                case STRFRAGS:
                    if (cfrag instanceof FragmentString)
                        tyfrags.add(cfrag);
                    break;
                case GLBFRAGS:
                    if (cfrag instanceof FragmentGlobal)
                        tyfrags.add(cfrag);
                    break;
                case CODEFRAGS:
                    if (cfrag instanceof FragmentCode) {
                        tyfrags.add(cfrag);
                    }
            }
        }

        return tyfrags;
    }

    public void visitResponse(String type, Object obj) {

	if (type.equals("IRMove")) {
	    visitMove(obj);
	}
        else if (type.equals("IRComment"))
            visitComment(obj);
	else if (type.equals("IRSeq")) {
	    visitSeq(obj);
	}
	else if (type.equals("IRJump")) {
	    visitJump(obj);
	}
	else if (type.equals("IRCJump")) {
	    visitCJump(obj);
	}
	else if (type.equals("IRLabel")) {
	    visitLabel(obj);
	}
	else if (type.equals("IRStatementExpression")) {
	    visitStmExp(obj);
	}
	else if (type.equals("IRConst")) {
	    visitConst(obj);
	}
	else if (type.equals("IRTemp")) {
	    visitTemp(obj);
	}
	else if (type.equals("IRBinop")) {
	    visitBinop(obj);
	}
	else if (type.equals("IRESeq")) {
	    visitESeq(obj);
	}
	else if (type.equals("IRStatementList")) {
	    visitStmList(obj);
	}
	else if (type.equals("IRMem")) {
	    visitMem(obj);
	}
        else if (type.equals("IRCall")) {
            visitCall(obj);
        }
        else if (type.equals("IRName")) {
            visitName(obj);
        }
        else {
            System.out.println("INTERNAL COMPILER ERROR: Skipping: " + type);
        }
    }

    private void visitStmExp(Object s) {

        IRStatementExpression is = (IRStatementExpression) s;

        munchExp(is.getExpression());
    }

    private void visitCall(Object c) {

        IRCall ic = (IRCall) c;
        m_lastTemp = munchExp((IRCall)ic);
    }

    private void visitMem(Object m) {

        IRMem im = (IRMem) m;
      //  Temp etemp = munchExp(im.getExpression());

        //etemp.setAsAddress();
        //m_lastTemp = etemp;

        m_lastTemp = munchExp(im);
    }

    private void visitName(Object n) {
        IRName in = (IRName) n;
        m_lastTemp = munchExp((IRName)in);
    }

    private void visitStmList(Object s) {

	IRStatementList as = (IRStatementList) s;
	int i;

	for (i = 0; i < as.getSize(); i++)  {
	    as.getStatement(i).visit(this);
	}
    }

    private void visitMove(Object s) {
	IRMove is = (IRMove)s;
        munchMove(is.getDestination(), is.getSource());

    }
    private void visitSeq(Object s) {
	IRSeq is = (IRSeq) s;
	munchSeq(is.getLeft(), is.getRight());
    }
    private void visitJump(Object j) {
	IRJump ij = (IRJump)j;
	munchJump(ij.getTarget());
    }
    private void visitCJump(Object cj) {
	IRCJump icj = (IRCJump)cj;
	munchCJump(icj.getRelationOperator(),icj.getLeft(),icj.getRight(),icj.getTrueLabel(),icj.getFalseLabel());
    }
    private void visitLabel(Object l) {
	IRLabel il = (IRLabel) l;
	munchLabel(il);
    }
    private void visitComment(Object c) {
        munchComment((IRComment)c);
    }

    private void munchComment(IRComment c) {
        emit(new OperInstruction("\t\t\t\t\t/* " + c.toString() + " */\n", null, null));
    }

    private void munchLabel(IRLabel l) {
        emit(new LabelInstruction("." + l.getLabel().toString() + ":\n",l.getLabel()));
    }

    private Temp munchExp(IRName n) {
        Temp t = m_curframe.allocLocalTemp();
        emit(new MoveInstruction("\tmovl\t$" + n.getLabel() + ", `d0\n", t, null, true));
        return t;
    }

    private void munchCJump(int operation, IRExpression left, IRExpression right, IRLabel truelabel, IRLabel falselabel) {

        Temp lefttemp,righttemp;

        lefttemp = munchExp(left);
        righttemp = munchExp(right);

        if (!m_regspill) {
            emit(new MoveInstruction("\tmovl\t`s0, `d0\n", m_curframe.getGeneralTemp(1), righttemp));
            emit(new MoveInstruction("\tmovl\t`s0, `d0\n", m_curframe.getGeneralTemp(2), lefttemp));
            emit(new OperInstruction("\tcmpl\t`s0, `s1\n", null, list(m_curframe.getGeneralTemp(1), m_curframe.getGeneralTemp(2))));
        }
        else {
            if (lefttemp.hasValue()) {
                if (lefttemp.getValue() == 0 && (operation == IRCJump.EQ || operation == IRCJump.NE)
                    && !righttemp.hasValue()) {
                    emit(new OperInstruction("\ttestl\t`s0, `s1\t\t/* Zero test */\n", null,
                                             list(righttemp,righttemp)));
                }
                else {
                    Temp ctemp = m_curframe.allocLocalTemp();
                    emit(new MoveInstruction(
                        "\tmovl\t`s0, `d0\t\t/* cmpl constant safety*/\n",
                        ctemp, lefttemp));
                    emit(new OperInstruction("\tcmpl\t`s0, `s1\t\t/* Const involved*/\n", null,
                                             list(righttemp, ctemp)));
                }
            }
            else {
                if (righttemp.hasValue() && righttemp.getValue() == 0 &&
                    (operation == IRCJump.EQ || operation == IRCJump.NE)) {
                    emit(new OperInstruction("\ttestl\t`s0, `s1\t\t/* Zero test */\n",null,
                                             list(lefttemp,lefttemp)));
                }
                else
                    emit(new OperInstruction("\tcmpl\t`s0, `s1\t\t/* Temp/Temp cmpl*/\n", null,
                                         list(righttemp, lefttemp)));
            }
        }

        if (operation == IRCJump.EQ) {
            emit(new OperInstruction("\tjne\t.`j0\n",null, null,list(falselabel.getLabel()),true));
        }
        else if (operation == IRCJump.GE) {
            emit(new OperInstruction("\tjl\t.`j0\n",null, null,list(falselabel.getLabel()),true));
        }
        else if (operation == IRCJump.GT) {
            emit(new OperInstruction("\tjle\t.`j0\n",null, null,list(falselabel.getLabel()),true));
        }
        else if (operation == IRCJump.LE) {
            emit(new OperInstruction("\tjg\t.`j0\n",null, null,list(falselabel.getLabel()),true));
        }
        else if (operation == IRCJump.LT) {
            emit(new OperInstruction("\tjge\t.`j0\n",null, null,list(falselabel.getLabel()),true));
        }
        else if (operation == IRCJump.NE) {
            emit(new OperInstruction("\tje\t.`j0\n",null, null,list(falselabel.getLabel()),true));
        }

    }

    private void munchJump(IRLabel target) {
	emit(new OperInstruction("\tjmp\t.`j0\n",null,null,list(target.getLabel()),false));
    }

    private boolean tryThinStore(IRExpression dst, IRExpression src) {
        if (dst instanceof IRMem) {
            IRExpression adr = ((IRMem)dst).getExpression();
            if (adr instanceof IRBinop &&
                ((IRBinop)adr).getRight() instanceof IRConst &&
                ((IRBinop)adr).getBinaryOperator() == IRBinop.PLUS) {
                Temp srctemp = munchExp(src);
                long v = ((IRConst)((IRBinop)adr).getRight()).getValue();
                Temp dsttemp = munchExp(((IRBinop)adr).getLeft());
                MoveInstruction m = new MoveInstruction("\tmovl\t`s0, " + v + "(`s1)\t\t/* Thin access*/\n",
                                         null, srctemp, true);
                m.addSource(dsttemp);
                emit(m);
                return true;
            }
        }
        return false;
    }

    private Temp tryArrayAccess(IRBinop exp) {
        IRExpression left = exp.getLeft();
        IRExpression right = exp.getRight();

        if (exp.getBinaryOperator() == IRBinop.PLUS && right instanceof IRBinop) {
            IRBinop br = (IRBinop) right;
            IRExpression brr = br.getRight();
            if (!(brr instanceof IRConst)) return null;
            int v = (int)((IRConst)brr).getValue();
            if (v != 1 && v != 2 && v != 4 && v != 8) return null;
            if (br.getBinaryOperator() == IRBinop.MUL &&
               brr instanceof IRConst && !(br.getLeft() instanceof IRConst)) {
               Temp r = m_curframe.allocLocalTemp();
               Temp lefttemp = munchExp(left);
               Temp multtemp = munchExp(br.getLeft());
               emit(new MoveInstruction("\tmovl\t`s0, `d0\t\t/* r move */\n", r , lefttemp));
               emit(new OperInstruction("\tleal\t(`s0, `s1, " + v + "), `d0\t/* Array Access */\n",
                                        list(r), list(r, multtemp)));
               return r;
            }
        }
        return null;

    }

    private boolean tryShiftMul(Temp r, Temp lefttemp, Temp righttemp) {
        int k;
        if (lefttemp.hasValue()) {
            int v = lefttemp.getValue();
            for (k = 1; k < 32; k++) {
                if (Math.pow(2, k) == v) {
                    Temp ktemp = m_curframe.allocLocalTemp();
                    ktemp.setValue(k);
                    emit(new MoveInstruction("\tmovl\t`s0, `d0\t\t/* r move */\n", r , righttemp));
                    emit(new OperInstruction("\tsall\t`s0, `d0\t\t/* Mult by " + v + " */\n",
                                             list(r), list(ktemp, r)));
                    return true;
                }
            }
        }
        else if (righttemp.hasValue()) {
            int v = righttemp.getValue();
            for (k = 1; k < 32; k++) {
                if (Math.pow(2, k) == v) {
                    Temp ktemp = m_curframe.allocLocalTemp();
                    ktemp.setValue(k);
                    emit(new MoveInstruction("\tmovl\t`s0, `d0\t\t/* r move */\n", r , lefttemp));
                    emit(new OperInstruction("\tsall\t`s0, `d0\t\t/* Mult by " + v + " */\n",
                                             list(r), list(ktemp, r)));
                    return true;
                }
            }
        }
        return false;
    }

    private boolean tryParamMove(IRExpression dst, IRExpression src) {
        Temp dsttemp;
        if (src instanceof IRMem) {
            IRExpression adsrc = ( (IRMem) src).getExpression();
            if (adsrc instanceof IRBinop) {
                IRBinop bmove = (IRBinop) adsrc;
                if (bmove.getBinaryOperator() == IRBinop.PLUS) {
                    Temp ltemp = munchExp(bmove.getLeft());
                    Temp rtemp = munchExp(bmove.getRight());
                    if (ltemp == m_curframe.getFrameTemp() &&
                        rtemp.hasValue() && ! (dst instanceof IRMem)) {
                        dsttemp = munchExp(dst);
                        MoveInstruction pfetch =
                            new MoveInstruction(
                            "\tmovl\t" + rtemp.getValue() +
                            "(`s1), `d0\t\t/* Param move */\n",
                            dsttemp, rtemp);
                        pfetch.addSource(ltemp);
                        emit(pfetch);
                        return true;
                    }
                }
            }
        }
        return false;
    }

    private boolean tryIncl(IRExpression dst, IRExpression src) {
        if (dst instanceof IRMem) return false;
        if (src instanceof IRBinop) {
            IRBinop op = (IRBinop) src;
            if (op.getBinaryOperator() == IRBinop.PLUS &&
                ((op.getLeft() instanceof IRConst && ((IRConst)op.getLeft()).getValue() == 1) ||
                 (op.getRight() instanceof IRConst && ((IRConst)op.getRight()).getValue() == 1) ||
                 (op.getLeft() instanceof IRConst && ((IRConst)op.getLeft()).getValue() == -1) ||
                 (op.getRight() instanceof IRConst && ((IRConst)op.getRight()).getValue() == -1))) {
                Temp rtemp = munchExp(op.getRight());
                Temp ltemp = munchExp(op.getLeft());
                Temp dsttemp = munchExp(dst);
                if (dsttemp == ltemp && rtemp.getValue() == 1) {
                    emit(new OperInstruction("\tincl\t`s0\t\t\t/* Add by 1*/\n", list(ltemp), list(ltemp)));
                    return true;
                }
                else if (dsttemp == rtemp && ltemp.getValue() == 1) {
                    emit(new OperInstruction("\tincl\t`s0\t\t\t/* Add by 1*/\n", list(rtemp), list(rtemp)));
                    return true;
                }
                else if (dsttemp == ltemp && rtemp.getValue() == -1) {
                    emit(new OperInstruction("\tdecl\t`s0\t\t\t/* Sub by 1*/\n", list(ltemp), list(ltemp)));
                    return true;
                }
                else if (dsttemp == rtemp && ltemp.getValue() == -1) {
                    emit(new OperInstruction("\tdecl\t`s0\t\t\t/* Sub by 1*/\n", list(rtemp), list(rtemp)));
                    return true;
                }

            }
        }
        return false;
    }

    private boolean tryAssignZero(IRExpression dst, IRExpression src) {
        if (src instanceof IRConst && ((IRConst)src).getValue() == 0 &&
            !(dst instanceof IRMem)) {
            Temp dsttemp = munchExp(dst);
            emit(new OperInstruction("\txorl\t`s0, `d0\t\t/* Set to zero */\n",
                                     list(dsttemp), list(dsttemp)));
            return true;
        }
        return false;
    }

    private void munchMove(IRExpression dst, IRExpression src) {

        Temp dsttemp;

        if (m_regspill && tryThinStore(dst, src)) return;
        if (m_regspill && tryAssignZero(dst, src)) return;
        if (m_regspill && tryParamMove(dst, src)) return;
        if (tryIncl(dst, src)) return;

	Temp srctemp = munchExp(src);
        if (dst instanceof IRMem) {
            dsttemp = munchExp(((IRMem)dst).getExpression());
            move(srctemp,dsttemp,src,dst);
        }
        else {
            dsttemp = munchExp(dst);
            move(srctemp,dsttemp,src,dst);
        }

    }

    private void munchSeq(IRStatement left, IRStatement right) {
	left.visit(this);
	right.visit(this);
    }

    private void visitConst(Object e) {
	IRConst ie = (IRConst) e;
	m_lastTemp = munchExp(ie);
    }
    private void visitTemp(Object t) {
	IRTemp it = (IRTemp)t;
	m_lastTemp = munchExp(it);
    }
    private void visitBinop(Object e) {
	IRBinop ie = (IRBinop)e;
	m_lastTemp = munchExp(ie);
    }

    private void visitESeq(Object e) {
       IRESeq ie = (IRESeq) e;

       ie.getEffect().visit(this);

       m_lastTemp = munchExp(ie.getExpression());
    }

    private Temp munchExp(IRExpression e) {
        Temp hit;
        if (null != (hit = (Temp) m_expmap.get(e)))
            return hit;
	e.visit(this);

        if (!(e instanceof IRName)) m_expmap.put(e, m_lastTemp);
	return m_lastTemp;
    }

    private Temp munchExp(IRTemp t) {
	return t.getTemp();
    }

    private Temp munchExp(IRConst n) {
	Temp r = m_curframe.allocLocalTemp();
	//emit(new MoveInstruction("\tmovl\t$"+(Integer.toString((int)n.getValue()))+", `d0\t\t/* Constant move (optimize!)*/\n",r,null));

        r.setValue((int)n.getValue());
	return r;
    }

    private Temp munchExp (IRBinop e) {

	Temp r = m_curframe.allocLocalTemp();
	Temp lefttemp,righttemp;
        Temp t;

        if (m_regspill && (null != (t = tryArrayAccess(e))))
            return t;

	lefttemp = munchExp(e.getLeft());
	righttemp = munchExp(e.getRight());

	switch (e.getBinaryOperator()) {
	    case IRBinop.PLUS:
                if (m_regspill) {
                    if (righttemp.hasValue() && righttemp.getValue() == 1) {
                        emit(new MoveInstruction("\tmovl\t`s0, `d0\t\t/* r move */\n", r , lefttemp));
                        emit(new OperInstruction(
                            "\tincl\t`s0\t\t\t/* Add by 1 */\n", list(r),
                            list(r)));
                    }
                    else {
                        emit(new MoveInstruction("\tmovl\t`s0, `d0\t\t/* r move */\n", r , lefttemp));
                        emit(new OperInstruction("\taddl\t`s0, `d0\t\t/* Addition */\n",
                                                 list(r), list(righttemp, r)));
                    }
                }
                else {
                    emit(new MoveInstruction("\tmovl\t`s0, `d0\n",m_curframe.getGeneralTemp(1), righttemp));
                    emit(new MoveInstruction("\tmovl\t`s0, `d0\n",m_curframe.getGeneralTemp(0), lefttemp));
                    emit(new OperInstruction("\tleal\t(`s0,`s1), `d0\n",list(m_curframe.getGeneralTemp(2)), list(m_curframe.getGeneralTemp(0),m_curframe.getGeneralTemp(1))));
                    emit(new MoveInstruction("\tmovl\t`s0, `d0\n",r, m_curframe.getGeneralTemp(2)));
                }
		break;
	    case IRBinop.MINUS:
                if (m_regspill) {
                    if (lefttemp.hasValue() && lefttemp.getValue() == 0) {
                        if (righttemp.hasValue()) {
                            righttemp.setValue(-righttemp.getValue());
                            emit(new MoveInstruction("\tmovl\t`s0, `d0\t\t/* r move */\n", r , righttemp));
                        }
                        else {
                            emit(new MoveInstruction("\tmovl\t`s0, `d0\t\t/* r move */\n", r,
                                righttemp));
                            emit(new OperInstruction("\tnegl\t`s0\t\t\t/* Negation */\n", list(r),
                                list(r)));
                        }
                    }
                    else {
                        emit(new MoveInstruction(
                            "\tmovl\t`s0, `d0\t\t/* r move */\n", r, lefttemp));
                        emit(new OperInstruction("\tsubl\t`s0, `d0\n", list(r),
                                                 list(righttemp, r)));
                    }
                }
                else {
                    emit(new MoveInstruction("\tmovl\t`s0, `d0\n",m_curframe.getGeneralTemp(2), righttemp));
                    emit(new MoveInstruction("\tmovl\t`s0, `d0\n",m_curframe.getGeneralTemp(0), lefttemp));
                    emit(new OperInstruction("\tsubl\t`s0, `d0\n",list(m_curframe.getGeneralTemp(0)),list(m_curframe.getGeneralTemp(2))));
                    emit(new MoveInstruction("\tmovl\t`s0, `d0\n",r, m_curframe.getGeneralTemp(0)));
                }
		break;

	    case IRBinop.MUL:
                if (m_regspill) {
                    if (!tryShiftMul(r, lefttemp, righttemp)) {
                        if (lefttemp.hasValue() && lefttemp.getValue() == 1)
                            emit(new MoveInstruction("\tmovl\t`s0, `d0\t\t/* Mult by 1*/\n",
                                r, righttemp));
                        else if (righttemp.hasValue() && righttemp.getValue() == 1)
                            emit(new MoveInstruction("\tmovl\t`s0, `d0\t\t/* Mult by 1*/\n",
                                r, lefttemp));
                        else {
                            emit(new MoveInstruction("\tmovl\t`s0, `d0\n", r,
                                lefttemp));
                            emit(new OperInstruction("\timull\t`s0, `d0\n",
                                list(r),
                                list(righttemp, r)));
                        }
                    }
                }
                else {
                    emit(new MoveInstruction("\tmovl\t`s0, `d0\n",m_curframe.getGeneralTemp(2), righttemp));
                    emit(new MoveInstruction("\tmovl\t`s0, `d0\n",m_curframe.getGeneralTemp(0), lefttemp));
                    emit(new OperInstruction("\timull\t`s0, `d0\n",list(m_curframe.getGeneralTemp(0)),list(m_curframe.getGeneralTemp(2))));
                    emit(new MoveInstruction("\tmovl\t`s0, `d0\n",r, m_curframe.getGeneralTemp(0)));
                }
		break;
	    case IRBinop.DIV:
                if (m_regspill) {
                    emit(new MoveInstruction("\tmovl\t`s0, `d0\n", m_curframe.getGeneralTemp(0), lefttemp));
                    emit(new OperInstruction("\tcltd\n", list(m_curframe.getGeneralTemp(1)), list(m_curframe.getGeneralTemp(0))));
                    if (righttemp.hasValue()) {
                        Temp ctemp = m_curframe.allocLocalTemp();
                        emit(new MoveInstruction("\tmovl\t`s0, `d0\t\t/* idivl const safety*/\n",
                                                 ctemp, righttemp));
                        emit(new OperInstruction("\tidivl\t`s0\n",list(m_curframe.getGeneralTemp(0),m_curframe.getGeneralTemp(1)), list(ctemp, m_curframe.getGeneralTemp(0),m_curframe.getGeneralTemp(1))));
                    }
                    else
                        emit(new OperInstruction("\tidivl\t`s0\n",list(m_curframe.getGeneralTemp(0),m_curframe.getGeneralTemp(1)), list(righttemp, m_curframe.getGeneralTemp(0),m_curframe.getGeneralTemp(1))));

                    emit(new MoveInstruction("\tmovl\t`s0, `d0\n",r, m_curframe.getGeneralTemp(0)));
                }
                else {
                    emit(new MoveInstruction("\tmovl\t`s0, `d0\n",m_curframe.getGeneralTemp(2), righttemp));
                    emit(new MoveInstruction("\tmovl\t`s0, `d0\n",m_curframe.getGeneralTemp(0), lefttemp));
                    emit(new OperInstruction("\tcltd\n", list(m_curframe.getGeneralTemp(1)), list(m_curframe.getGeneralTemp(0))));
                    emit(new OperInstruction("\tidivl\t`s0\n",list(m_curframe.getGeneralTemp(0),m_curframe.getGeneralTemp(1)), list(m_curframe.getGeneralTemp(2))));
                    emit(new MoveInstruction("\tmovl\t`s0, `d0\n",r, m_curframe.getGeneralTemp(0)));
                }
		break;
	    case IRBinop.MOD:
                if (m_regspill) {
                    emit(new MoveInstruction("\tmovl\t`s0, `d0\n",m_curframe.getGeneralTemp(0), lefttemp));
                    emit(new OperInstruction("\tcltd\n", list(m_curframe.getGeneralTemp(1)), list(m_curframe.getGeneralTemp(0))));
                    if (righttemp.hasValue()) {
                        Temp ctemp = m_curframe.allocLocalTemp();
                        emit(new MoveInstruction("\tmovl\t`s0, `d0\t\t/* idivl const safety*/\n",
                                                 ctemp, righttemp));
                        emit(new OperInstruction("\tidivl\t`s0\n",list(m_curframe.getGeneralTemp(0),m_curframe.getGeneralTemp(1)), list(ctemp, m_curframe.getGeneralTemp(0),m_curframe.getGeneralTemp(1))));
                    }
                    else
                        emit(new OperInstruction("\tidivl\t`s0\n",list(m_curframe.getGeneralTemp(0),m_curframe.getGeneralTemp(1)), list(righttemp, m_curframe.getGeneralTemp(0),m_curframe.getGeneralTemp(1))));

                    emit(new MoveInstruction("\tmovl\t`s0, `d0\n",r, m_curframe.getGeneralTemp(1)));
                }
                else {
                    emit(new MoveInstruction("\tmovl\t`s0, `d0\n",m_curframe.getGeneralTemp(2), righttemp));
                    emit(new MoveInstruction("\tmovl\t`s0, `d0\n",m_curframe.getGeneralTemp(0), lefttemp));
                    emit(new OperInstruction("\tcltd\n", list(m_curframe.getGeneralTemp(1)), list(m_curframe.getGeneralTemp(0))));
                    emit(new OperInstruction("\tidivl\t`s0\n",list(m_curframe.getGeneralTemp(0),m_curframe.getGeneralTemp(1)), list(m_curframe.getGeneralTemp(2))));
                    emit(new MoveInstruction("\tmovl\t`s0, `d0\n",r, m_curframe.getGeneralTemp(1)));
                }
		break;
            case IRBinop.BAND:
                if (m_regspill) {
                    emit(new MoveInstruction("\tmovl\t`s0, `d0\t\t/* r move */\n", r , lefttemp));
                    emit(new OperInstruction("\tandl\t`s0, `d0\n",list(r),list(righttemp, r)));
                }
                else {
                    emit(new MoveInstruction("\tmovl\t`s0, `d0\n",m_curframe.getGeneralTemp(1), righttemp));
                    emit(new MoveInstruction("\tmovl\t`s0, `d0\n",m_curframe.getGeneralTemp(2), lefttemp));
                    emit(new OperInstruction("\tandl\t`s0, `d0\n",list(m_curframe.getGeneralTemp(2)),list(m_curframe.getGeneralTemp(1))));
                    emit(new MoveInstruction("\tmovl\t`s0, `d0\n",r, m_curframe.getGeneralTemp(2)));
                }
		break;
            case IRBinop.BOR:
                if (m_regspill) {
                    emit(new MoveInstruction("\tmovl\t`s0, `d0\t\t/* r move */\n", r , lefttemp));
                    emit(new OperInstruction("\torl\t`s0, `d0\n",list(r),list(righttemp, r)));
                }
                else {
                    emit(new MoveInstruction("\tmovl\t`s0, `d0\n",m_curframe.getGeneralTemp(1), righttemp));
                    emit(new MoveInstruction("\tmovl\t`s0, `d0\n",m_curframe.getGeneralTemp(2), lefttemp));
                    emit(new OperInstruction("\torl\t`s0, `d0\n",list(m_curframe.getGeneralTemp(2)),list(m_curframe.getGeneralTemp(1))));
                    emit(new MoveInstruction("\tmovl\t`s0, `d0\n",r, m_curframe.getGeneralTemp(2)));
                }
             break;
            case IRBinop.BXOR:
                if (m_regspill) {
                    emit(new MoveInstruction("\tmovl\t`s0, `d0\t\t/* r move */\n", r , lefttemp));
                    emit(new OperInstruction("\txorl\t`s0, `d0\n",list(r),list(righttemp, r)));
                }
                else {
                    emit(new MoveInstruction("\tmovl\t`s0, `d0\n",m_curframe.getGeneralTemp(1), righttemp));
                    emit(new MoveInstruction("\tmovl\t`s0, `d0\n",m_curframe.getGeneralTemp(2), lefttemp));
                    emit(new OperInstruction("\txorl\t`s0, `d0\n",list(m_curframe.getGeneralTemp(2)),list(m_curframe.getGeneralTemp(1))));
                    emit(new MoveInstruction("\tmovl\t`s0, `d0\n",r, m_curframe.getGeneralTemp(2)));
                }
		break;
            case IRBinop.LSHIFT:
                          if (m_regspill) {
                              if (righttemp.hasValue() && righttemp.getValue() >= 0 && righttemp.getValue() < 32) {
                                  emit(new MoveInstruction("\tmovl\t`s0, `d0\t\t/* r move */\n", r , lefttemp));
                                  emit(new OperInstruction("\tsall\t`s0, `d0\t\t/* k based shift */\n",
                                                           list(r), list(righttemp, r)));
                              }
                              else {
                                  emit(new MoveInstruction("\tmovl\t`s0, `d0\t\t/* r move */\n", r , lefttemp));
                                  emit(new MoveInstruction("\tmovl\t`s0, `d0\t\t/* ECX|Shift move*/\n",m_curframe.getGeneralTemp(2), righttemp));
                                  emit(new OperInstruction("\tsall\t%cl, `d0\n",list(r), list(m_curframe.getGeneralTemp(2), r)));
                              }
                          }
                          else {
                              emit(new MoveInstruction("\tmovl\t`s0, `d0\n",m_curframe.getGeneralTemp(2), righttemp));
                              emit(new MoveInstruction("\tmovl\t`s0, `d0\n",m_curframe.getGeneralTemp(1), lefttemp));
                              emit(new OperInstruction("\tsall\t%cl, `d0\n",list(m_curframe.getGeneralTemp(1)), list(m_curframe.getGeneralTemp(2))));
                              emit(new MoveInstruction("\tmovl\t`s0, `d0\n",r, m_curframe.getGeneralTemp(1)));
                          }
                          break;
                      case IRBinop.RSHIFT:
                          if (m_regspill) {
                              if (righttemp.hasValue() && righttemp.getValue() >= 0 && righttemp.getValue() < 32) {
                                  emit(new MoveInstruction("\tmovl\t`s0, `d0\t\t/* r move */\n", r , lefttemp));
                                  emit(new OperInstruction("\tsarl\t`s0, `d0\t\t/* k based shift*/\n",
                                                           list(r), list(righttemp, r)));
                              }
                              else {
                                  emit(new MoveInstruction("\tmovl\t`s0, `d0\t\t/* r move */\n", r , lefttemp));
                                  emit(new MoveInstruction("\tmovl\t`s0, `d0\t\t/* ECX|Shift move*/\n",m_curframe.getGeneralTemp(2), righttemp));
                                  emit(new OperInstruction("\tsarl\t%cl, `d0\n",list(r), list(m_curframe.getGeneralTemp(2), r)));
                              }
                          }
                          else {
                              emit(new MoveInstruction("\tmovl\t`s0, `d0\n",m_curframe.getGeneralTemp(2), righttemp));
                              emit(new MoveInstruction("\tmovl\t`s0, `d0\n",m_curframe.getGeneralTemp(1), lefttemp));
                              emit(new OperInstruction("\tsarl\t%cl, `d0\n",list(m_curframe.getGeneralTemp(1)), list(m_curframe.getGeneralTemp(2))));
                              emit(new MoveInstruction("\tmovl\t`s0, `d0\n",r, m_curframe.getGeneralTemp(1)));
                          }
                          break;

	    default:
		throw new Error("Codegen.munchExp(BINOP)");
	}

	return r;
    }

    private Temp munchExp(IRMem im) {

        Temp deref = m_curframe.allocLocalTemp();

        Temp exptmp = munchExp(im.getExpression());

        if (!m_regspill) {
            emit(new MoveInstruction("\tmovl\t`s0, `d0\n",
                                     m_curframe.getGeneralTemp(0), exptmp));
            emit(new MoveInstruction("\tmovl\t(`s0), `d0\n",
                                     m_curframe.getGeneralTemp(1),
                                     m_curframe.getGeneralTemp(0), true));
            emit(new MoveInstruction("\tmovl\t`s0, `d0\n", deref,
                                     m_curframe.getGeneralTemp(1)));
        }
        else {
            emit(new MoveInstruction("\tmovl\t(`s0), `d0\t\t/* Mem fetch */\n", deref, exptmp, true));
        }

        return deref;
    }

    private Temp munchExp(IRCall ic) {

        int i;
        IRExpressionList params = ic.getParams();
        Temp rt = m_curframe.allocLocalTemp();
        List csaves = new ArrayList();
        csaves.add(m_curframe.getGeneralTemp(0)); csaves.add(m_curframe.getGeneralTemp(1));
        csaves.add(m_curframe.getGeneralTemp(2)); csaves.add(m_curframe.getReturnTemp());

        for (i = params.getSize()-1; i >= 0; i--) {
            Temp ptemp = munchExp(params.getExpression(i));
            emit(new OperInstruction("\tpushl\t`s0\n",null,list(ptemp)));
        }
        emit(new OperInstruction("\tcall\t"+ic.getName().getName()+"\n",csaves, null));
        if (params.getSize() > 0)
            emit(new OperInstruction("\taddl\t$"+params.getSize()*m_curframe.getWordSize()+",%esp\n",null,null));
        emit(new MoveInstruction("\tmovl\t`s0, `d0\t\t/* Return value deposit */\n", rt, m_curframe.getReturnTemp()));

        return rt;
    }

    private List list(Object o, Object p) {
        List v = new ArrayList();
        v.add(o); v.add(p);
        return v;
    }

    private List list(Object o, Object p, Object q) {
        List v = new ArrayList();
        v.add(o); v.add(p); v.add(q);
        return v;
    }

    private List list(Object o) {
	List v = new ArrayList();
	v.add(o);
	return v;
    }

    private void emit(Instruction x) {
	m_instrs.add(x);
    }

    private void move(Temp srctemp, Temp dsttemp, IRExpression src, IRExpression dst) {
        if (!m_regspill) {
            emit(new MoveInstruction("\tmovl\t`s0, `d0\n",m_curframe.getGeneralTemp(0),srctemp));
            if (dst instanceof IRMem) {
                emit(new MoveInstruction("\tmovl\t`s0, `d0\n",m_curframe.getGeneralTemp(1),dsttemp));
                MoveInstruction memmove =
                    new MoveInstruction("\tmovl\t`s0, (`s1)\n",null,m_curframe.getGeneralTemp(0),true);
                memmove.addSource(m_curframe.getGeneralTemp(1));
                emit(memmove);
            }
            else {
                emit(new MoveInstruction("\tmovl\t`s0, `d0\n",dsttemp,m_curframe.getGeneralTemp(0)));
            }
        }
        else {
            if (dst instanceof IRMem) {
                MoveInstruction memmove = new MoveInstruction("\tmovl\t`s0, (`s1)\t\t/* Mem store */\n",
                    null, srctemp , true);
                memmove.addSource(dsttemp);
                emit(memmove);
            }
            else {
                emit(new MoveInstruction("\tmovl\t`s0, `d0\n",dsttemp,srctemp));
            }
        }
    }

    private Dictionary m_expmap = new Hashtable();
    private Temp m_lastTemp;
    private List m_instrs;
    private Frame m_curframe;
    private boolean m_regspill;
}
