// $Id: MIPSCodegen.java,v 1.7 1999/12/10 14:29:10 mpp Exp $

package IR2;

import java6035.tools.ASM.*;
import java.io.*;

public class MIPSCodegen implements Codegen {

  private MIPSGenerator mipsgen;
  private OutputStream out;
  private int arg_count, stack_padding, callee_regs_saved;

  // For params passed in regs, the regs they go in. 0 based.
  private MIPSDelocalizedRegister param_registers[];
  private MIPSRegister gp;

  // Stack pointer and function value return register
  private MIPSDelocalizedRegister stack_pointer, frame_pointer, 
    return_register, return_address;

  // Scratch registers for intermediate values.
  private MIPSDelocalizedRegister scratch_registers[];

  // Spare registers that temporaries can be allocated in.
  private MIPSDelocalizedRegister[] spare_registers;

  // We keep two sequences of ASM instructions going at a time.
  // This is to make the output prettier should we need to
  // switch between code and data alot (i.e. to deal with a
  // String literal).
  private ASM data_asm, code_asm;
  private int data_area_offset;


  public MIPSCodegen(int format_code, OutputStream output) {
    data_asm = new ASM();
    data_asm.appendEntry(new dData());
    data_asm.appendEntry(new ASMLabel("data_base"));
    data_area_offset = 0;
    gp = MIPSRegister.s7;
    

    code_asm = new ASM();
    code_asm.appendEntry(new dText());

    out = output;

    mipsgen = new MIPSGenerator(format_code, out);

    // n32 calling convention
    param_registers = new MIPSDelocalizedRegister[8];
    param_registers[0] = new MIPSDelocalizedRegister(MIPSRegister.a0);
    param_registers[1] = new MIPSDelocalizedRegister(MIPSRegister.a1);
    param_registers[2] = new MIPSDelocalizedRegister(MIPSRegister.a2);
    param_registers[3] = new MIPSDelocalizedRegister(MIPSRegister.a3);
    param_registers[4] = new MIPSDelocalizedRegister(MIPSRegister.t0);
    param_registers[5] = new MIPSDelocalizedRegister(MIPSRegister.t1);
    param_registers[6] = new MIPSDelocalizedRegister(MIPSRegister.t2);
    param_registers[7] = new MIPSDelocalizedRegister(MIPSRegister.t3);

    // SP!
    stack_pointer = new MIPSDelocalizedRegister(MIPSRegister.sp);

    // return_reg
    return_register = new MIPSDelocalizedRegister(MIPSRegister.v0);

    // FP!
    frame_pointer = new MIPSDelocalizedRegister(MIPSRegister.fp);

    // return address register
    return_address = new MIPSDelocalizedRegister(MIPSRegister.ra);

    // scratch registers for intermediate values
    scratch_registers = new MIPSDelocalizedRegister[3];
    scratch_registers[0] = return_register;
    //scratch_registers[0] = new MIPSDelocalizedRegister(MIPSRegister.t7);
    scratch_registers[1] = new MIPSDelocalizedRegister(MIPSRegister.t8);
    scratch_registers[2] = new MIPSDelocalizedRegister(MIPSRegister.t9);

    // all registers that don't have any other purpose
    spare_registers = new MIPSDelocalizedRegister[7];
    spare_registers[0] = new MIPSDelocalizedRegister(MIPSRegister.s0);
    spare_registers[1] = new MIPSDelocalizedRegister(MIPSRegister.s1);
    spare_registers[2] = new MIPSDelocalizedRegister(MIPSRegister.s2);
    spare_registers[3] = new MIPSDelocalizedRegister(MIPSRegister.s3);
    spare_registers[4] = new MIPSDelocalizedRegister(MIPSRegister.s4);
    spare_registers[5] = new MIPSDelocalizedRegister(MIPSRegister.s5);
    spare_registers[6] = new MIPSDelocalizedRegister(MIPSRegister.s6);
    //spare_registers[7] = new MIPSDelocalizedRegister(MIPSRegister.s7);

    // Dummy main.
    generate_label("main");
    code_asm.appendEntry(MIPSOpcode.la_instr(gp,new MIPSAddress("data_base")));
    generate_jal(new DelocalizedLabelInstruction("_main"));
    prep_for_callout(0);
    generate_callout(new DelocalizedLabelInstruction("exit"));
  }


  // How to get results!
  public DelocalizedRegister function_return_register() {
    return return_register;
  }



  // This procedure is called during the LowIR -> DelocalizedIR
  // pass, unlike most of the others.  It generates whatever
  // DelocalizedIR instructions necessary for setting up a stack frame.
  //
  // For the MIPS codegen module, this saves the params in registers
  // into several temporary vars (conceptually on the stack).
  public void generate_delocalized_prologue(MethodDescriptor d, CFG output) {
    ParamScalarVarDescriptor[] parameters = d.get_parameters();
    for (int i = 0; i < param_registers.length && i < parameters.length; i++) {
      TempVarDescriptor t = new TempVarDescriptor(d, "$MIPS_n32_param$" + i);
      parameters[i].set_proxy(t);
      output.addElement(new DelocalizedStoreInstruction
                        (t, param_registers[i]));
    }
  }



  // The procedure "r" is guaranteed to return a valid "scratch" register.
  // There is always guaranteed to be at least three valid scratch registers,
  // so c.r(0), c.r(1), and c.r(2) always ought to return valid
  // registers.
  public DelocalizedRegister r(int n) {
    if (0 <= n && n < 3)
      return scratch_registers[n];
    return null;
  }

  // The procedure "allocatable_registers" returns an array of spare registers
  // (ones that can be allocated in register allocation).
  public DelocalizedRegister[] allocatable_registers() {
    return spare_registers;
  }


  // Reserves space for n zeroed words at the start of the .data area.
  // This is really just generate_zeroed_words.  I like this name better :P
  public void reserve_zeroed_data_area(int n) {
    data_asm.appendEntry(new dSpace(4*n));
    data_area_offset += 4*n;
  }

  // Add the string data to data_asm; return a location which points
  // to the beginning of the string in global memory.
  public int allocate_string(String value) {

    // First backslashify the value.
	StringBuffer buf = new StringBuffer(value.length());
	for (int i = 0; i < value.length(); i++) {
	  char c = value.charAt(i);
	  switch (c) {
	  case '\'':
		buf.append("\\\'");
		break;
	  case '\"':
		buf.append("\\\"");
		break;
	  case '\\':
		buf.append("\\\\");
		break;
	  case '\n':
		buf.append("\\n");
		break;
	  default:
		buf.append(c);
		break;
	  }
	}

    // Stick the value in the data segment.
    data_asm.appendEntry(new dAsciiz(buf.toString()));

    // Update the end of the data segment and return the location.
    int location = data_area_offset;
    data_area_offset += value.length() + 1;
    //data_area_offset = ((data_area_offset+3)/4)*4;
    return location;
  }



  public void generate_move(DelocalizedRegister Rdest, DelocalizedRegister Rsrc){
    code_asm.appendEntry(MIPSOpcode.move_instr
                         ( ((MIPSDelocalizedRegister) Rdest).get_register(),
                           ((MIPSDelocalizedRegister)  Rsrc).get_register() ));
  }

  public void generate_load_immediate(DelocalizedRegister Rdest, long value) {
    code_asm.appendEntry(MIPSOpcode.li_instr
                         ( ((MIPSDelocalizedRegister) Rdest).get_register(),
                           (int) value ));
  }

  public void generate_load_address(DelocalizedRegister Rdest, long value) {
    code_asm.appendEntry(MIPSOpcode.la_instr
                         ( ((MIPSDelocalizedRegister) Rdest).get_register(),
                           new MIPSAddress("data_base + " + (int) value) ));
  }

  public void generate_la(DelocalizedRegister Rdest, long address) {
    code_asm.appendEntry(MIPSOpcode.la_instr
                         ( ((MIPSDelocalizedRegister) Rdest).get_register(),
                           new MIPSAddress((int) address)));
  }

  public void generate_lw(DelocalizedRegister Rdest, long address) {
    code_asm.appendEntry(MIPSOpcode.lw_instr
                         ( ((MIPSDelocalizedRegister) Rdest).get_register(),
                           new MIPSAddress((int) address)));
  }

  public void generate_sw(DelocalizedRegister Rsrc, long address) {
    code_asm.appendEntry(MIPSOpcode.sw_instr
                         ( ((MIPSDelocalizedRegister) Rsrc).get_register(),
                           new MIPSAddress((int) address)));
  }
  public void generate_lw(DelocalizedRegister Rdest, MIPSAddress address) {
    code_asm.appendEntry(MIPSOpcode.lw_instr
                         ( ((MIPSDelocalizedRegister) Rdest).get_register(),
                           address));
  }

  public void generate_sw(DelocalizedRegister Rsrc, MIPSAddress address) {
    code_asm.appendEntry(MIPSOpcode.sw_instr
                         ( ((MIPSDelocalizedRegister) Rsrc).get_register(),
                           address));
  }
  // Throw a val into the appropriate return register
  public void generate_return(DelocalizedRValue val){
    val.rproxy().load_into_reg(this, return_register);
  }

  public void generate_label(String label_name) {
    code_asm.appendEntry(new ASMLabel(label_name));
  }


  /* Calling Convention: n32 for both calls and callouts. 
     CALLEE RESPONSIBILITIES
     
     Prologue
     1. Save $fp and let $fp = $sp
     2. Save other callee-saved registers if any. ($$ra)
     3. $sp = $sp - frame_size (for locals and temps)

     Epilogue
     1. Restore callee-saved regs
     2. Pop stack frame by ading frame size to $sp
     3. Return (jump to $ra)
  */

  public void generate_prologue(String name, int stack_size) {
    // For debuggers
    code_asm.appendEntry(new dEnt("_" + name));

    // Save $fp, then let $fp = $sp
    generate_sw(frame_pointer, 
                new MIPSAddress(stack_pointer.get_register(), -4));
    generate_move(frame_pointer, stack_pointer);
    
    // Save $ra
    generate_sw(return_address,
                new MIPSAddress(stack_pointer.get_register(), -8));

    // Save callee-saved regs $s0-$s7 here.  XXX should insert smarter code
    // to only save the ones we used.
    for (int i = 0; i < spare_registers.length; i++) {
      generate_sw(spare_registers[i],
                  new MIPSAddress(stack_pointer.get_register(), -12 - 4*i));
    }

    // Save more callee regs here.
    this.callee_regs_saved = 2 + spare_registers.length;

    // Compute effective stack size and include padding
    int size = callee_regs_saved + stack_size;
    if (size % 4 != 0) size += (4 - size % 4);
    
    // Move sp over stack_size + words used to save callee regs
    code_asm.appendEntry(MIPSOpcode.sub_instr(stack_pointer.get_register(),
                                              stack_pointer.get_register(),
                                              new MIPSImmed(size * 4)));
    comment(name + " begins here");
  }

  public void generate_epilogue(String name, int stack_size) {
    comment(name + "'s epilogue");
    
    // Restore $sp
    generate_move(stack_pointer, frame_pointer);
    
    // Restore saved callee args
    generate_lw(frame_pointer, 
                new MIPSAddress(stack_pointer.get_register(), -4));
    generate_lw(return_address,
                new MIPSAddress(stack_pointer.get_register(), -8));
    // Restore callee-saved regs $s0-$s7 here.  XXX should insert smarter code
    // to only save the ones we used.  Also we shouldn't assume that
    // spare_registers is all $sN.
    for (int i = 0; i < spare_registers.length; i++) {
      generate_lw(spare_registers[i],
                  new MIPSAddress(stack_pointer.get_register(), -12 - 4*i));
    }
    
    // Get out of here!
    generate_jr(return_address);

    code_asm.appendEntry(new dEnd("_" + name));
  }

  public void generate_array_fetch(DelocalizedRegister dest,
                                   int array_offset,
                                   DelocalizedRegister index) {
    // stored at gp+(array_offset*4)+(index*4)
    generate_load_immediate(r(2), 4);
    generate_mulo(index, index, r(2));
    // this whole gp thing is a hack.
    code_asm.appendEntry(new MIPSInstruction(MIPSOpcode.add,
                           ((MIPSDelocalizedRegister) index).get_register(),
                           gp,
                           ((MIPSDelocalizedRegister) index).get_register()));
    generate_lw(dest, new MIPSAddress
                (((MIPSDelocalizedRegister) index).get_register(),
                 array_offset*4));
  }

  public void generate_array_store(int array_offset,
                                   DelocalizedRegister index,
                                   DelocalizedRegister src) {
    // stored at gp+(array_offset*4)+(index*4)
    generate_load_immediate(r(2), 4);
    generate_mulo(index, index, r(2));
    // this whole gp thing is a hack.
    code_asm.appendEntry(new MIPSInstruction(MIPSOpcode.add,
                           ((MIPSDelocalizedRegister) index).get_register(),
                           gp,
                           ((MIPSDelocalizedRegister) index).get_register()));
    generate_sw(src, new MIPSAddress
                (((MIPSDelocalizedRegister) index).get_register(),
                 array_offset*4));
  }

  public void generate_load_global(DelocalizedRegister r, int offset){
    // this whole gp thing is a hack.
    MIPSAddress foo = new MIPSAddress(gp, offset*4);
    generate_lw(r, foo);
  }
  public void generate_save_global(int offset, DelocalizedRegister r){
    // this whole gp thing is a hack.
    MIPSAddress foo = new MIPSAddress(gp, offset*4);
    generate_sw(r, foo);
  }

  // uses frame pointer (fp) as register, offset as offset
  public void generate_load_local(DelocalizedRegister r, int offset){
    MIPSAddress foo = new MIPSAddress(MIPSRegister.fp, 
                                      -(offset+1+callee_regs_saved)*4);
    this.generate_lw(r, foo);
  }
  public void generate_save_local(int offset, DelocalizedRegister r){
    MIPSAddress foo = new MIPSAddress(MIPSRegister.fp, 
                                      -(offset+1+callee_regs_saved)*4);
    this.generate_sw(r, foo);
  }

  public void generate_load_param(DelocalizedRegister r, int param_num){
    if (param_num < param_registers.length) {
      /* can't happen --- see generate_delocalized_prologue */
    } else {
      generate_lw(r, new MIPSAddress(MIPSRegister.fp,
                                    (param_num - param_registers.length)*8+4));
    }
  }

  public void generate_save_param(int param_num, DelocalizedRegister r){
    if (param_num < param_registers.length) {
      /* can't happen --- see generate_delocalized_prologue */
    } else {
      generate_sw(r, new MIPSAddress(MIPSRegister.fp,
                                    (param_num - param_registers.length)*8+4));
    }
  }


  // Arith ops

  public void generate_add(DelocalizedRegister Rdest,
                           DelocalizedRegister Rsrc1,
                           DelocalizedRegister Src2){
    code_asm.appendEntry(new MIPSInstruction(MIPSOpcode.add,
                           ((MIPSDelocalizedRegister) Rdest).get_register(),
                           ((MIPSDelocalizedRegister) Rsrc1).get_register(),
                           ((MIPSDelocalizedRegister) Src2).get_register()));
  }

  public void generate_mulo(DelocalizedRegister Rdest,
                           DelocalizedRegister Rsrc1,
                           DelocalizedRegister Src2){

     code_asm.appendEntry(new MIPSInstruction(MIPSOpcode.mulo,
                           ((MIPSDelocalizedRegister) Rdest).get_register(),
                           ((MIPSDelocalizedRegister) Rsrc1).get_register(),
                           ((MIPSDelocalizedRegister) Src2).get_register()));
   }

  public void generate_shl(DelocalizedRegister Rdest,
                           DelocalizedRegister Rsrc1,
                           DelocalizedRegister Src2){
     code_asm.appendEntry(new MIPSInstruction(MIPSOpcode.sll,
                           ((MIPSDelocalizedRegister) Rdest).get_register(),
                           ((MIPSDelocalizedRegister) Rsrc1).get_register(),
                           ((MIPSDelocalizedRegister) Src2).get_register()));
  }

  public void generate_shr(DelocalizedRegister Rdest,
                           DelocalizedRegister Rsrc1,
                           DelocalizedRegister Src2){
     code_asm.appendEntry(new MIPSInstruction(MIPSOpcode.srl,
                           ((MIPSDelocalizedRegister) Rdest).get_register(),
                           ((MIPSDelocalizedRegister) Rsrc1).get_register(),
                           ((MIPSDelocalizedRegister) Src2).get_register()));
  }

  public void generate_sub(DelocalizedRegister Rdest,
                           DelocalizedRegister Rsrc1,
                           DelocalizedRegister Src2){
     code_asm.appendEntry(new MIPSInstruction(MIPSOpcode.sub,
                           ((MIPSDelocalizedRegister) Rdest).get_register(),
                           ((MIPSDelocalizedRegister) Rsrc1).get_register(),
                           ((MIPSDelocalizedRegister) Src2).get_register()));
  }

  // Boolean math instructions

  public void generate_seq(DelocalizedRegister Rdest,
                           DelocalizedRegister Rsrc1,
                           DelocalizedRegister Src2){
     code_asm.appendEntry(new MIPSInstruction(MIPSOpcode.seq,
                           ((MIPSDelocalizedRegister) Rdest).get_register(),
                           ((MIPSDelocalizedRegister) Rsrc1).get_register(),
                           ((MIPSDelocalizedRegister) Src2).get_register()));
  }

  public void generate_sge(DelocalizedRegister Rdest,
                           DelocalizedRegister Rsrc1,
                           DelocalizedRegister Src2){
     code_asm.appendEntry(new MIPSInstruction(MIPSOpcode.sge,
                           ((MIPSDelocalizedRegister) Rdest).get_register(),
                           ((MIPSDelocalizedRegister) Rsrc1).get_register(),
                           ((MIPSDelocalizedRegister) Src2).get_register()));
  }

  public void generate_sgt(DelocalizedRegister Rdest,
                           DelocalizedRegister Rsrc1,
                           DelocalizedRegister Src2){
     code_asm.appendEntry(new MIPSInstruction(MIPSOpcode.sgt,
                           ((MIPSDelocalizedRegister) Rdest).get_register(),
                           ((MIPSDelocalizedRegister) Rsrc1).get_register(),
                           ((MIPSDelocalizedRegister) Src2).get_register()));
  }

  public void generate_sle(DelocalizedRegister Rdest,
                           DelocalizedRegister Rsrc1,
                           DelocalizedRegister Src2){
     code_asm.appendEntry(new MIPSInstruction(MIPSOpcode.sle,
                           ((MIPSDelocalizedRegister) Rdest).get_register(),
                           ((MIPSDelocalizedRegister) Rsrc1).get_register(),
                           ((MIPSDelocalizedRegister) Src2).get_register()));
  }

  public void generate_slt(DelocalizedRegister Rdest,
                           DelocalizedRegister Rsrc1,
                           DelocalizedRegister Src2){
     code_asm.appendEntry(new MIPSInstruction(MIPSOpcode.slt,
                           ((MIPSDelocalizedRegister) Rdest).get_register(),
                           ((MIPSDelocalizedRegister) Rsrc1).get_register(),
                           ((MIPSDelocalizedRegister) Src2).get_register()));
  }

  public void generate_sne(DelocalizedRegister Rdest,
                           DelocalizedRegister Rsrc1,
                           DelocalizedRegister Src2){
    code_asm.appendEntry(new MIPSInstruction(MIPSOpcode.sne,
                           ((MIPSDelocalizedRegister) Rdest).get_register(),
                           ((MIPSDelocalizedRegister) Rsrc1).get_register(),
                           ((MIPSDelocalizedRegister) Src2).get_register()));
  }


  // Arith ops with immeds

  public void generate_add(DelocalizedRegister Rdest,
                           DelocalizedRegister Rsrc1,
                           DelocalizedImmed Src2){
    code_asm.appendEntry(new MIPSInstruction(MIPSOpcode.add,
                           ((MIPSDelocalizedRegister) Rdest).get_register(),
                           ((MIPSDelocalizedRegister) Rsrc1).get_register(),
                           new MIPSImmed(((DelocalizedImmed) Src2).get_value())));
  }

  public void generate_mulo(DelocalizedRegister Rdest,
                           DelocalizedRegister Rsrc1,
                           DelocalizedImmed Src2){

     code_asm.appendEntry(new MIPSInstruction(MIPSOpcode.mulo,
                           ((MIPSDelocalizedRegister) Rdest).get_register(),
                           ((MIPSDelocalizedRegister) Rsrc1).get_register(),
                           new MIPSImmed(((DelocalizedImmed) Src2).get_value())));
   }

  public void generate_shl(DelocalizedRegister Rdest,
                           DelocalizedRegister Rsrc1,
                           DelocalizedImmed Src2){
     code_asm.appendEntry(new MIPSInstruction(MIPSOpcode.sll,
                           ((MIPSDelocalizedRegister) Rdest).get_register(),
                           ((MIPSDelocalizedRegister) Rsrc1).get_register(),
                           new MIPSImmed(((DelocalizedImmed) Src2).get_value())));
  }

  public void generate_shr(DelocalizedRegister Rdest,
                           DelocalizedRegister Rsrc1,
                           DelocalizedImmed Src2){
     code_asm.appendEntry(new MIPSInstruction(MIPSOpcode.srl,
                           ((MIPSDelocalizedRegister) Rdest).get_register(),
                           ((MIPSDelocalizedRegister) Rsrc1).get_register(),
                           new MIPSImmed(((DelocalizedImmed) Src2).get_value())));
  }

  public void generate_sub(DelocalizedRegister Rdest,
                           DelocalizedRegister Rsrc1,
                           DelocalizedImmed Src2){
     code_asm.appendEntry(new MIPSInstruction(MIPSOpcode.sub,
                           ((MIPSDelocalizedRegister) Rdest).get_register(),
                           ((MIPSDelocalizedRegister) Rsrc1).get_register(),
                           new MIPSImmed(((DelocalizedImmed) Src2).get_value())));
  }

  // Boolean math instructions with immeds

  public void generate_seq(DelocalizedRegister Rdest,
                           DelocalizedRegister Rsrc1,
                           DelocalizedImmed Src2){
     code_asm.appendEntry(new MIPSInstruction(MIPSOpcode.seq,
                           ((MIPSDelocalizedRegister) Rdest).get_register(),
                           ((MIPSDelocalizedRegister) Rsrc1).get_register(),
                           new MIPSImmed(((DelocalizedImmed) Src2).get_value())));
  }

  public void generate_sge(DelocalizedRegister Rdest,
                           DelocalizedRegister Rsrc1,
                           DelocalizedImmed Src2){
     code_asm.appendEntry(new MIPSInstruction(MIPSOpcode.sge,
                           ((MIPSDelocalizedRegister) Rdest).get_register(),
                           ((MIPSDelocalizedRegister) Rsrc1).get_register(),
                           new MIPSImmed(((DelocalizedImmed) Src2).get_value())));
  }

  public void generate_sgt(DelocalizedRegister Rdest,
                           DelocalizedRegister Rsrc1,
                           DelocalizedImmed Src2){
     code_asm.appendEntry(new MIPSInstruction(MIPSOpcode.sgt,
                           ((MIPSDelocalizedRegister) Rdest).get_register(),
                           ((MIPSDelocalizedRegister) Rsrc1).get_register(),
                           new MIPSImmed(((DelocalizedImmed) Src2).get_value())));
  }

  public void generate_sle(DelocalizedRegister Rdest,
                           DelocalizedRegister Rsrc1,
                           DelocalizedImmed Src2){
     code_asm.appendEntry(new MIPSInstruction(MIPSOpcode.sle,
                           ((MIPSDelocalizedRegister) Rdest).get_register(),
                           ((MIPSDelocalizedRegister) Rsrc1).get_register(),
                           new MIPSImmed(((DelocalizedImmed) Src2).get_value())));
  }

  public void generate_slt(DelocalizedRegister Rdest,
                           DelocalizedRegister Rsrc1,
                           DelocalizedImmed Src2){
     code_asm.appendEntry(new MIPSInstruction(MIPSOpcode.slt,
                           ((MIPSDelocalizedRegister) Rdest).get_register(),
                           ((MIPSDelocalizedRegister) Rsrc1).get_register(),
                           new MIPSImmed(((DelocalizedImmed) Src2).get_value())));
  }

  public void generate_sne(DelocalizedRegister Rdest,
                           DelocalizedRegister Rsrc1,
                           DelocalizedImmed Src2){
    code_asm.appendEntry(new MIPSInstruction(MIPSOpcode.sne,
                           ((MIPSDelocalizedRegister) Rdest).get_register(),
                           ((MIPSDelocalizedRegister) Rsrc1).get_register(),
                           new MIPSImmed(((DelocalizedImmed) Src2).get_value())));
  }



  // Branch instructions

  public void generate_b(DelocalizedLabelInstruction lbl){
    code_asm.appendEntry(new MIPSInstruction(MIPSOpcode.b,
                                             new MIPSAddress(lbl.get_label()),
                                             null,
                                             null));
  }

  public void generate_b(String lbl){
    code_asm.appendEntry(new MIPSInstruction(MIPSOpcode.b,
                                             new MIPSAddress(lbl),
                                             null,
                                             null));
  }

  public void generate_beq(DelocalizedRegister Rsrc1,
                           DelocalizedRegister Src2,
                           DelocalizedLabelInstruction lbl){
    code_asm.appendEntry(new MIPSInstruction(MIPSOpcode.beq,
                                             new MIPSAddress(lbl.get_label()),
                                             ((MIPSDelocalizedRegister)Rsrc1).get_register(),
                                             ((MIPSDelocalizedRegister)Src2).get_register()));
  }

  public void generate_bgt(DelocalizedRegister Rsrc1,
                           DelocalizedRegister Src2,
                           DelocalizedLabelInstruction lbl){
    code_asm.appendEntry(new MIPSInstruction(MIPSOpcode.bgt,
                                             new MIPSAddress(lbl.get_label()),
                                             ((MIPSDelocalizedRegister)Rsrc1).get_register(),
                                             ((MIPSDelocalizedRegister)Src2).get_register()));
  }

  public void generate_bgt(DelocalizedRegister Rsrc1,
                           DelocalizedLabelInstruction lbl){
    code_asm.appendEntry(new MIPSInstruction(MIPSOpcode.bgt,
                                             new MIPSAddress(lbl.get_label()),
                                             ((MIPSDelocalizedRegister)Rsrc1).get_register(),
                                             MIPSRegister.r0));
  }

  public void generate_bge(DelocalizedRegister Rsrc1,
                           DelocalizedRegister Src2,
                           DelocalizedLabelInstruction lbl){
    code_asm.appendEntry(new MIPSInstruction(MIPSOpcode.bge,
                                             new MIPSAddress(lbl.get_label()),
                                             ((MIPSDelocalizedRegister)Rsrc1).get_register(),
                                             ((MIPSDelocalizedRegister)Src2).get_register()));
  }

  public void generate_blt(DelocalizedRegister Rsrc1,
                           DelocalizedRegister Src2,
                           DelocalizedLabelInstruction lbl){
    code_asm.appendEntry(new MIPSInstruction(MIPSOpcode.blt,
                                             new MIPSAddress(lbl.get_label()),
                                             ((MIPSDelocalizedRegister)Rsrc1).get_register(),
                                             ((MIPSDelocalizedRegister)Src2).get_register()));
  }

  public void generate_ble(DelocalizedRegister Rsrc1,
                           DelocalizedRegister Src2,
                           DelocalizedLabelInstruction lbl){
    code_asm.appendEntry(new MIPSInstruction(MIPSOpcode.ble,
                                             new MIPSAddress(lbl.get_label()),
                                             ((MIPSDelocalizedRegister)Rsrc1).get_register(),
                                             ((MIPSDelocalizedRegister)Src2).get_register()));
  }

  public void generate_bne(DelocalizedRegister Rsrc1,
                           DelocalizedRegister Src2,
                           DelocalizedLabelInstruction lbl){
    code_asm.appendEntry(new MIPSInstruction(MIPSOpcode.bne,
                                             new MIPSAddress(lbl.get_label()),
                                             ((MIPSDelocalizedRegister)Rsrc1).get_register(),
                                             ((MIPSDelocalizedRegister)Src2).get_register()));
  }

  public void generate_bne(DelocalizedRegister Rsrc1,
                           DelocalizedLabelInstruction lbl){
    code_asm.appendEntry(new MIPSInstruction(MIPSOpcode.bne,
                                             new MIPSAddress(lbl.get_label()),
                                             ((MIPSDelocalizedRegister)Rsrc1).get_register(),
                                             MIPSRegister.r0));
  }

  public void generate_j(DelocalizedLabelInstruction lbl){
    code_asm.appendEntry(MIPSOpcode.j_instr(lbl.get_label()));
  }

  public void generate_jal(DelocalizedLabelInstruction lbl){
    code_asm.appendEntry(MIPSOpcode.jal_instr(lbl.get_label()));
  }

  public void generate_jal(DelocalizedRegister Rsrc1){
    code_asm.appendEntry(MIPSOpcode.jal_instr(((MIPSDelocalizedRegister)Rsrc1).get_register()));
  }
                          
  public void generate_jr(DelocalizedRegister Rsrc1){
    code_asm.appendEntry(MIPSOpcode.jr_instr(((MIPSDelocalizedRegister)Rsrc1).get_register()));

  }

  /* Calling Convention: n32 for both calls and callouts. 
     CALLER RESPONSIBILITIES

     1. Pass the arguments. arg0-arg3 go in registers a0-a3 respectively.
        arg4-arg7 go in t0-t3 respectively. Other arguments go on the stack,
        last one first (so that the last arg is lowest on the stack, and the
        9th one is right at the top at $fp+4).

     2. Save the caller-saved registers: any of a0-a3 and t0-t9 that
        have live values we want to save.  
           - For now: assume all are live.
        Put this on the stack. Make call. After call, pop from
        stack.  */
     
  // Number of words needing to be saved
  private int num_callersaves() {
    return 0;
//    return param_registers.length;
  }

  private void generate_save_callersaves() {
//      for (int i=0; i < param_registers.length; i++)
//        push(param_registers[i]);
  }

  private void generate_restore_callersaves() {
//      // Must restore things that were in regs.

//      increment_sp(param_registers.length);

//      // POSSIBLE MATH ERROR HERE
//      for (int i=0; i < param_registers.length; i++)
//        code_asm.appendEntry(MIPSOpcode.lw_instr(
//                              param_registers[i].get_register(),
//                              new MIPSAddress(stack_pointer.get_register(),
//                                              (-i-1)*4)));
  }

  public void prep_for_callout(int count) {
    prep_for_call(count);
  }

  public void prep_for_call(int count) {

    this.arg_count = count;

    // Compute # caller_saves
    int words = num_callersaves();

    // Move sp to where it needs to be to receive caller saves and 
    // count-8 args and still be 16-BYTE ALIGNED (n32)
    count -= 8; if (count < 0) count = 0;
    count *= 2; // On the stack in n32, each word is 64 bits!
    this.stack_padding =(words+count)% 4;
    if (this.stack_padding > 0) this.stack_padding = 4 - this.stack_padding;

    comment("Function call: " + arg_count + " args, "+words+" callersaves, "+
            stack_padding + " padding");

    decrement_sp(this.stack_padding);

    // Do caller_saves
    generate_save_callersaves();

    // Make space for args
    decrement_sp(count);

    comment("Placement of arguments");
  }
  
  public void generate_call_arg(DelocalizedRValue arg, int arg_num) {
    // n32 calling convention
    if (arg_num < param_registers.length) {
      arg.rproxy().load_into_reg(this, param_registers[arg_num]);
    } else {
      // store at sp + 4*(arg_num - 8)
      arg.rproxy().load_into_reg(this, r(0));
      generate_sw(r(0), new MIPSAddress(stack_pointer.get_register(), 
                                        8 * (arg_num - 8) + 4));
    }
  }

  public void generate_callout_arg(DelocalizedRValue arg, int arg_num) {
    generate_call_arg(arg, arg_num);
  }

  public void generate_call(DelocalizedLabelInstruction target) {
    generate_jal(target);
    
    comment("Clean up from call");

    // Now pop stuff.
    if (arg_count > 8)
      increment_sp((arg_count-8)*2);

    generate_restore_callersaves();

    increment_sp(stack_padding);
    comment("Function call sequence completed");
  }

  public void generate_callout(DelocalizedLabelInstruction target) {
    code_asm.appendEntry(MIPSOpcode.callout_instr(target.get_label(),
                                                  arg_count));
    comment("Clean up from callout");

    // Now pop stuff.
    if (arg_count > 8)
      increment_sp((arg_count-8)*2);

    generate_restore_callersaves();

    increment_sp(stack_padding);
    comment("Function callout sequence completed");
  }

  // Stack manipulation routines
  /*
  private void push(DelocalizedRegister src) {
    
    generate_sw(src, new MIPSAddress(stack_pointer.get_register(), -4));
    decrement_sp(1);
  }
  */
  private void increment_sp(int words) {
    if (words == 0) return;
    code_asm.appendEntry(MIPSOpcode.add_instr(stack_pointer.get_register(),
                                              stack_pointer.get_register(),
                                              new MIPSImmed(4*words)));
  }

  private void decrement_sp(int words) {
    if (words == 0) return;
    code_asm.appendEntry(MIPSOpcode.sub_instr(stack_pointer.get_register(),
                                              stack_pointer.get_register(),
                                              new MIPSImmed(4*words)));
  }

  public void comment(String s) {
    code_asm.appendEntry(new MIPSInstruction(MIPSOpcode.NOP, null, null, 
                                             null, s));
  }

  // Actually output code to file.
  public void output_code() throws IOException {
    // Create runtime exception handlers.
    generate_label("array_bounds_runtime_exception");
    prep_for_callout(1);
    generate_callout_arg(new DelocalizedStringImmed("Runtime exception: array index out of bounds.  Aborting.\n"), 0);
    generate_callout(new DelocalizedLabelInstruction("printf"));
    prep_for_callout(0);
    generate_callout(new DelocalizedLabelInstruction("exit"));

    generate_label("fell_off_end_runtime_exception");
    prep_for_callout(1);
    generate_callout_arg(new DelocalizedStringImmed("Runtime exception: control fell off the end of a non-void procedure.  Aborting.\n"), 0);
    generate_callout(new DelocalizedLabelInstruction("printf"));
    prep_for_callout(0);
    generate_callout(new DelocalizedLabelInstruction("exit"));


    // Actually generate the output file and then close it
    mipsgen.gen(data_asm);
    mipsgen.gen(code_asm);
    mipsgen.done();
    out.close();
  }
}
