/**
  * For a given string representing a valid operand encoding for the Assembler, return the
  * corresponding Assembler constant. This function only looks for encodings of operand types, and
  * will not accept strings that correspond to size encodings.
  *
  * @param str A valid Assembler encoding of operand type
  * @return The Assembler constant corresponding to str, or -1 if none
  */
 private static ArgumentType getEncoding(String str, Class<?> type) {
   if (str.equals("Reg")) {
     if (type == null) {
       throw new Error("Unable to encode Reg with no type information");
     }
     String typeName = type.getName();
     str = typeName.substring(typeName.lastIndexOf('$') + 1) + "_Reg";
   }
   for (ArgumentType arg : ArgumentType.values()) {
     if (arg.getOptName().equals(str)) {
       return arg;
     }
   }
   throw new Error(
       "Unable to encode the argument " + str + " of type " + type + " as a valid argument type");
 }
    /**
     * Find the best operand type or size and operand number to partition this EmitterSet. This
     * method searches across all possible ways of splitting this set--all possible operand types
     * and sizes, and all possible operands--to determine which one splits the set most evenly.
     *
     * @return a SplitRecord representing the most-even split
     */
    SplitRecord split() {
      int splitArg = -1;
      ArgumentType splitTest = null;
      int splitDiff = 1000;
      for (int arg = 0; arg < 4; arg++) {
        for (ArgumentType test : ArgumentType.values()) {
          int c = getEncodingSplit(arg, test);
          if (c == 0) return new SplitRecord(arg, test);
          else if (c < splitDiff) {
            splitArg = arg;
            splitTest = test;
            splitDiff = c;
          }
        }
      }

      return new SplitRecord(splitArg, splitTest);
    }
  /** Generate an assembler for the opt compiler */
  public static void main(String[] args) {
    try {
      out = new FileWriter(System.getProperty("generateToDir") + "/AssemblerOpt.java");
    } catch (IOException e) {
      throw new Error(e);
    }

    emit("package org.jikesrvm.compilers.opt.mir2mc.ia32;\n\n");
    emit("import org.jikesrvm.*;\n\n");
    emit("import org.jikesrvm.compilers.opt.*;\n\n");
    emit("import org.jikesrvm.compilers.opt.ir.*;\n\n");
    emit("import org.jikesrvm.compilers.opt.ir.ia32.*;\n\n");
    emit("import static org.jikesrvm.compilers.opt.ir.ia32.ArchOperators.*;\n\n");
    emit("import static org.jikesrvm.compilers.opt.OptimizingCompilerException.opt_assert;\n\n");
    emit("\n\n");

    emit("/**\n");
    emit(" *  This class is the automatically-generated assembler for\n");
    emit(" * the optimizing compiler.  It consists of methods that\n");
    emit(" * understand the possible operand combinations of each\n");
    emit(" * instruction type, and how to translate those operands to\n");
    emit(" * calls to the Assember low-level emit method\n");
    emit(" *\n");
    emit(" * It is generated by GenerateAssembler.java\n");
    emit(" *\n");
    emit(" */\n");
    emit("public class AssemblerOpt extends AssemblerBase {\n\n");

    emitTab(1);
    emit("/**\n");
    emitTab(1);
    emit(" * @see org.jikesrvm.ArchitectureSpecific.Assembler\n");
    emitTab(1);
    emit(" */\n");
    emitTab(1);
    emit("public AssemblerOpt(int bcSize, boolean print, IR ir) {\n");
    emitTab(2);
    emit("super(bcSize, print, ir);\n");
    emitTab(1);
    emit("}");
    emit("\n\n");

    Method[] emitters = lowLevelAsm.getDeclaredMethods();
    Set<String> opcodes = getOpcodes(emitters);

    Iterator<String> i = opcodes.iterator();
    while (i.hasNext()) {
      String opcode = (String) i.next();
      setCurrentOpcode(opcode);
      emitTab(1);
      emit("/**\n");
      emitTab(1);
      emit(" *  Emit the given instruction, assuming that\n");
      emitTab(1);
      emit(" * it is a " + currentFormat + " instruction\n");
      emitTab(1);
      emit(" * and has a " + currentOpcode + " operator\n");
      emitTab(1);
      emit(" *\n");
      emitTab(1);
      emit(" * @param inst the instruction to assemble\n");
      emitTab(1);
      emit(" */\n");
      emitTab(1);
      emit("private void do" + opcode + "(Instruction inst) {\n");
      EmitterSet emitter = buildSetForOpcode(emitters, opcode);
      boolean[][] tp = new boolean[4][ArgumentType.values().length];
      emitter.emitSet(opcode, tp, 2);
      emitTab(1);
      emit("}\n\n");
    }

    emitTab(1);
    emit("/**\n");
    emitTab(1);
    emit(" *  The number of instructions emitted so far\n");
    emitTab(1);
    emit(" */\n");
    emitTab(1);
    emit("private int instructionCount = 0;\n\n");

    emitTab(1);
    emit("/**\n");
    emitTab(1);
    emit(" *  Assemble the given instruction\n");
    emitTab(1);
    emit(" *\n");
    emitTab(1);
    emit(" * @param inst the instruction to assemble\n");
    emitTab(1);
    emit(" */\n");
    emitTab(1);
    emit("public void doInst(Instruction inst) {\n");
    emitTab(2);
    emit("instructionCount++;\n");
    emitTab(2);
    emit("resolveForwardReferences(instructionCount);\n");
    emitTab(2);
    emit("switch (inst.getOpcode()) {\n");

    Set<String> emittedOpcodes = new HashSet<String>();

    i = opcodes.iterator();
    while (i.hasNext()) {
      String opcode = i.next();
      Iterator<String> operators = getMatchingOperators(opcode).iterator();
      while (operators.hasNext()) {
        String operator = operators.next();
        emitTab(3);
        emittedOpcodes.add(operator);
        emit("case IA32_" + operator + "_opcode:\n");
      }
      emitTab(4);
      emit("do" + opcode + "(inst);\n");
      emitTab(4);
      emit("break;\n");
    }

    // Special case because doJCC is handwritten to add
    // logic for short-forward branches
    emittedOpcodes.add("JCC");
    emitTab(3);
    emit("case IA32_JCC_opcode:\n");
    emitTab(4);
    emit("doJCC(inst);\n");
    emitTab(4);
    emit("break;\n");

    // Special case because doJMP is handwritten to add
    // logic for short-forward branches
    emittedOpcodes.add("JMP");
    emitTab(3);
    emit("case IA32_JMP_opcode:\n");
    emitTab(4);
    emit("doJMP(inst);\n");
    emitTab(4);
    emit("break;\n");

    // Kludge for IA32_LOCK which needs to call emitLockNextInstruction
    emittedOpcodes.add("LOCK");
    emitTab(3);
    emit("case IA32_LOCK_opcode:\n");
    emitTab(4);
    emit("emitLockNextInstruction();\n");
    emitTab(4);
    emit("break;\n");

    // Kludge for PATCH_POINT
    emitTab(3);
    emit("case IG_PATCH_POINT_opcode:\n");
    emitTab(4);
    emit("emitPatchPoint();\n");
    emitTab(4);
    emit("break;\n");

    // Kludge for LOWTABLESWITCH
    emitTab(3);
    emit("case MIR_LOWTABLESWITCH_opcode:\n");
    emitTab(4);
    emit("doLOWTABLESWITCH(inst);\n");
    emitTab(4);
    emit("// kludge table switches that are unusually long instructions\n");
    emitTab(4);
    emit("instructionCount += MIR_LowTableSwitch.getNumberOfTargets(inst);\n");
    emitTab(4);
    emit("break;\n");

    Set<String> errorOpcodes = getErrorOpcodes(emittedOpcodes);
    if (!errorOpcodes.isEmpty()) {
      i = errorOpcodes.iterator();
      while (i.hasNext()) {
        emitTab(3);
        emit("case IA32_" + i.next() + "_opcode:\n");
      }
      emitTab(4);
      emit(
          "throw new OptimizingCompilerException(inst + \" has unimplemented IA32 opcode (check excludedOpcodes)\");\n");
    }

    emitTab(2);
    emit("}\n");
    emitTab(2);
    emit("inst.setmcOffset( mi );\n");
    emitTab(1);
    emit("}\n\n");

    emit("\n}\n");

    try {
      out.close();
    } catch (IOException e) {
      throw new Error(e);
    }
  }