/**
  * Optimizations for OP_convert_i: convert_i, convert_i -> convert_i coerce_i, convert_i ->
  * coerce_i OP_bitand, convert_i -> OP_bitand OP_bitor, convert_i -> OP_bitor OP_bitxor, convert_i
  * -> OP_bitxor lshift, convert_i -> lshift rshift, convert_i -> rshift add_i, convert_i -> add_i
  * subtract_i, convert_i -> subtract_i increment_i, convert_i -> increment_i decrement_i,
  * convert_i -> decrement_i multiply_i, convert_i -> multiply_i pushbyte, convert_i -> pushbyte
  * pushshort, convert_i -> pushshort pushint, convert_i -> pushint li8, convert_i -> li8 li16,
  * convert_i -> li16 li32, convert_i -> li32 sxi1, convert_i -> sxi1 sxi8, convert_i -> sxi8
  * sxi16, convert_i -> sxi16
  *
  * @param i The convert_b instruction
  */
 private void op_convert_i(InstructionInfo i) {
   InstructionInfo prev = previous(1);
   switch (prev.getOpcode()) {
     case OP_convert_i:
     case OP_coerce_i:
     case OP_bitand:
     case OP_bitor:
     case OP_bitxor:
     case OP_lshift:
     case OP_rshift:
     case OP_add_i:
     case OP_subtract_i:
     case OP_increment_i:
     case OP_decrement_i:
     case OP_multiply_i:
     case OP_pushbyte:
     case OP_pushshort:
     case OP_pushint:
     case OP_li8:
     case OP_li16:
     case OP_li32:
     case OP_sxi1:
     case OP_sxi8:
     case OP_sxi16:
       {
         // result is already an int, just erase the op_convert_i
         delete(0);
         break;
       }
     default:
       {
         // nothing to do - instruction has already been added
       }
   }
 }
 /**
  * Optimizations for OP_convert_b: equals, convert_b -> equals strictequals, convert_b ->
  * strictequals not, convert_b -> not greaterthan, convert_b -> greaterthan lessthan, convert_b ->
  * lessthan greaterequals, convert_b -> greaterequals lessequals, convert_b -> lessequals istype,
  * convert_b -> istype istypelate, convert_b -> istypelate instanceof, convert_b -> instanceof
  * deleteproperty, convert_d -> deleteproperty in, convert_d -> in convert_b, convert_b ->
  * convert_b pushtrue, convert_b -> pushtrue pushfalse, convert_b -> pushfalse
  *
  * @param i The convert_b instruction
  */
 private void op_convert_b(InstructionInfo i) {
   InstructionInfo prev = previous(1);
   switch (prev.getOpcode()) {
     case OP_equals:
     case OP_strictequals:
     case OP_not:
     case OP_greaterthan:
     case OP_lessthan:
     case OP_greaterequals:
     case OP_lessequals:
     case OP_istype:
     case OP_istypelate:
     case OP_instanceof:
     case OP_deleteproperty:
     case OP_in:
     case OP_convert_b:
     case OP_pushtrue:
     case OP_pushfalse:
       {
         // result is already a boolean, just erase the op_convert_b
         delete(0);
         break;
       }
     default:
       {
         // nothing to do - instruction has already been added
       }
   }
 }
 /**
  * Optimizations for OP_returnvoid: returnvoid, returnvoid -> returnvoid getlocal0, pushscope,
  * returnvoid -> returnvoid (insn without side effect), returnvoid -> returnvoid
  */
 private void op_returnvoid(InstructionInfo i) {
   InstructionInfo prev = previous(1);
   switch (prev.getOpcode()) {
     case OP_returnvoid:
       {
         delete(0);
         break;
       }
       //  Any instruction without side effects that
       //  the CG is likely to generate can go here.
     case OP_pop:
       {
         replace(1, i.getInstruction());
         break;
       }
     case OP_pushscope:
       {
         if (previous(2).getOpcode() == OP_getlocal0)
           //  Trivial function.
           replace(2, i.getInstruction());
         break;
       }
     default:
       {
         // nothing to do, instruction has already been added
       }
   }
 }
 /**
  * Optimizations for OP_getproperty: findpropstrict name, getproperty name -> getlex name
  *
  * @param i the getproperty instruction
  */
 private void op_getproperty(InstructionInfo i) {
   InstructionInfo prev = previous(1);
   switch (prev.getOpcode()) {
     case OP_findpropstrict:
       {
         // can optimize findpropstrict, followed by getprop of the same name
         // if there are any instructions btwn the findpropstrict and getprop (such as to compute a
         // runtime
         // multiname), we won't match this pattern
         if (i.getOperand(0).equals(prev.getOperand(0)))
           replace(
               1, InstructionFactory.createModifiedInstruction(OP_getlex, prev.getInstruction()));
         break;
       }
   }
 }
 /**
  * Optimizations for OP_convert_i: convert_u, convert_u -> convert_u pushuint, convert_u ->
  * pushuint
  *
  * @param i The convert_b instruction
  */
 private void op_convert_u(InstructionInfo i) {
   InstructionInfo prev = previous(1);
   switch (prev.getOpcode()) {
     case OP_convert_u:
     case OP_pushuint:
       {
         // result is already a uint, just erase the op_convert_u
         delete(0);
         break;
       }
     default:
       {
         // nothing to do - instruction has already been added
       }
   }
 }
 /**
  * Optimizations for OP_convert_s: coerce_s, convert_s -> coerce_s convert_s, convert_s ->
  * convert_s pushstring, convert_s -> pushstring typeof, convert_s -> typeof
  *
  * @param i The convert_b instruction
  */
 private void op_convert_s(InstructionInfo i) {
   InstructionInfo prev = previous(1);
   switch (prev.getOpcode()) {
     case OP_coerce_s:
     case OP_convert_s:
     case OP_pushstring:
     case OP_typeof:
       {
         // result is already a String, just erase the op_convert_s
         delete(0);
         break;
       }
     default:
       {
         // nothing to do - instruction has already been added
       }
   }
 }
  /**
   * Helper method to perform optimizations around jumps when we get a labeling operation. Shared
   * with labelNext and labelCurrent methods
   *
   * @param l the Label object passed into the label operation
   * @param kind what kind of labeling operation are we doing (labelNext or labelCurrent)
   */
  private void jumpOptimizations(Label l, LabelKind kind) {
    // Need to start looking at different indexes for label current vs. label next
    // this is because for a label next, the next instruction won't have come in yet.
    int idx = kind == LabelKind.LABEL_CURRENT ? 1 : 0;

    InstructionInfo prev = previous(idx);
    switch (prev.getOpcode()) {
      case OP_jump:
        {
          InstructionInfo prev2 = previous(idx + 1);
          Instruction insn = prev2.getInstruction();
          if (insn != null && insn.isBranch() && insn.getTarget() == l) {
            // If the previous instructions were an if that jumped here, and it
            // only jumped over another jump, then we can invert the if instruction
            // and save a jump
            //   iffalse L1, jump L2, L1    -> iftrue L2, L1
            Instruction newIf = invertIf(prev2, prev);
            if (newIf != null) {
              if (kind == LabelKind.LABEL_CURRENT) {
                // labelCurrent, so we need to preserve the last instruction
                Instruction[] newInsns = {newIf, previous(0).getInstruction()};
                replace(idx + 1, newInsns);
              } else {
                // labelNext so we can just delete the last instruction
                replace(idx + 1, newIf);
              }
            }
          }
          // If the previous instruction was a jump, and it just jumped
          // to the next instruction, then we can remove the jump and just fall
          // through
          //   jump L1, L1 -> L1
          else if (prev.getOperand(0) == l) {
            if (kind == LabelKind.LABEL_NEXT)
              // can just delete the jump because we don't have the next instruction yet
              delete(idx);
            else
              // replace the jump with its target
              replace(idx, previous(0).getInstruction());
          }
        }
    }
  }
 /**
  * evaluates an instruction and determines if it will result in true or false on TOS.
  *
  * @param i is an instruction to analyze
  * @return whether TOS is true, false, or not known
  */
 private static ConstantBoolean isTrueInstructionInfo(InstructionInfo i) {
   ConstantBoolean ret = ConstantBoolean.DONT_KNOW;
   switch (i.getOpcode()) {
     case OP_pushtrue:
       ret = ConstantBoolean.TRUE;
       break;
     case OP_pushfalse:
       ret = ConstantBoolean.FALSE;
       break;
     case OP_pushbyte:
       {
         int value = i.getImmediate();
         assert value >= 0;
         ret = ECMASupport.toBoolean(value) ? ConstantBoolean.TRUE : ConstantBoolean.FALSE;
         break;
       }
     case OP_pushint:
       {
         int value = (Integer) i.getOperand(0);
         ret = ECMASupport.toBoolean(value) ? ConstantBoolean.TRUE : ConstantBoolean.FALSE;
         break;
       }
     case OP_pushuint:
       {
         long value = (Long) i.getOperand(0);
         ret = ECMASupport.toBoolean(value) ? ConstantBoolean.TRUE : ConstantBoolean.FALSE;
         break;
       }
     case OP_pushstring:
       {
         String value = i.getOperand(0).toString();
         ret = ECMASupport.toBoolean(value) ? ConstantBoolean.TRUE : ConstantBoolean.FALSE;
         break;
       }
     case OP_pushnull:
       ret = ConstantBoolean.FALSE;
       break;
   }
   return ret;
 }
  /** Optimizations for OP_getlocal: setlocal N, getlocal N -> dup, setlocal N */
  private void OP_getlocal(InstructionInfo i) {
    InstructionInfo prev = previous(1);
    InstructionInfo cur = previous(0);

    switch (prev.getOpcode()) {
      case OP_setlocal:
        {
          if (cur.getInstruction().getImmediate() != prev.getInstruction().getImmediate())
            break; // set,get of different locals

          Instruction[] newInsns = {
            InstructionFactory.getInstruction(OP_dup), prev.getInstruction()
          };
          replace(1, newInsns);
          break;
        }
      default:
        {
          // nothing to do, instruction has already been added
        }
    }
  }
 /**
  * Optimizations for OP_convert_d: convert_d, convert_d -> convert_d pushbyte n, convert_d ->
  * pushdouble n pushint n, convert_d -> pushdouble n pushuint n, convert_d -> pushdouble n
  * pushdouble n, convert_d -> pushdouble n pushnan, convert_d -> pushnan lf32, convert_d -> lf32
  * lf64, convert_d -> lf64
  *
  * @param i The convert_d instruction
  */
 private void op_convert_d(InstructionInfo i) {
   InstructionInfo prev = previous(1);
   switch (prev.getOpcode()) {
     case OP_pushbyte:
       {
         // replace pushbyte, convert d with pushdouble - should be faster
         replace(
             1,
             InstructionFactory.getInstruction(
                 OP_pushdouble, new Double(convertByteImmediateToDouble(prev.getImmediate()))));
         break;
       }
     case OP_pushint:
     case OP_pushuint:
       {
         // replace pushint , convert d with pushdouble - should be faster
         replace(
             1,
             InstructionFactory.getInstruction(
                 OP_pushdouble, new Double(((Number) prev.getOperand(0)).doubleValue())));
         break;
       }
     case OP_pushdouble:
     case OP_pushnan:
     case OP_lf32:
     case OP_lf64:
     case OP_convert_d:
       {
         // result is already a double, just erase the op_convert_d
         delete(0);
         break;
       }
     default:
       {
         // nothing to do - instruction has already been added
       }
   }
 }
 /** Optimizations for OP_pop: callprop, pop -> callpropvoid callsuper, pop -> callsupervoid */
 private void op_pop(InstructionInfo i) {
   InstructionInfo prev = previous(1);
   switch (prev.getOpcode()) {
     case OP_callproperty:
       {
         replace(
             1,
             InstructionFactory.createModifiedInstruction(OP_callpropvoid, prev.getInstruction()));
         break;
       }
     case OP_callsuper:
       {
         replace(
             1,
             InstructionFactory.createModifiedInstruction(
                 OP_callsupervoid, prev.getInstruction()));
         break;
       }
     default:
       {
         // nothing to do, instruction has already been added
       }
   }
 }
  /**
   * Optimizations for OP_iftrue: convert_b, iftrue -> iftrue equals, iftrue -> ifeq strictequals,
   * iftrue -> ifstricteq lessthen, iftrue -> iflt lessequals, iftrue -> ifle greaterthan, iftrue ->
   * ifgt greaterequals, iftrue -> ifge pushfalse, iftrue -> jump pushtrue, iftrue -> nothing
   * strictequals, not, iftrue -> ifstrictne equals, not, iftrue -> ifne lessthan, not, iftrue ->
   * ifnlt lessequals, not, iftrue -> ifnle greaterthan, not, iftrue -> ifngt greaterequals,
   * not,iftrue -> ifnge not, iftrue -> iffalse
   *
   * @param i the iftrue instruction
   */
  private void op_iftrue(InstructionInfo i) {
    InstructionInfo prev = previous(1);

    switch (isTrueInstructionInfo(prev)) {
      case FALSE:
        delete(1);
        return;
      case TRUE:
        replace(1, InstructionFactory.createModifiedInstruction(OP_jump, i.getInstruction()));
        return;
      case DONT_KNOW:
        break; // continue on if we don't know it's a constant boolean
      default:
        assert false;
    }
    switch (prev.getOpcode()) {
      case OP_convert_b:
        {
          replace(1, i.getInstruction());
          break;
        }
      case OP_equals:
        {
          replace(1, InstructionFactory.createModifiedInstruction(OP_ifeq, i.getInstruction()));
          break;
        }
      case OP_strictequals:
        {
          replace(
              1, InstructionFactory.createModifiedInstruction(OP_ifstricteq, i.getInstruction()));
          break;
        }
      case OP_lessthan:
        {
          replace(1, InstructionFactory.createModifiedInstruction(OP_iflt, i.getInstruction()));
          break;
        }
      case OP_lessequals:
        {
          replace(1, InstructionFactory.createModifiedInstruction(OP_ifle, i.getInstruction()));
          break;
        }
      case OP_greaterthan:
        {
          replace(1, InstructionFactory.createModifiedInstruction(OP_ifgt, i.getInstruction()));
          break;
        }
      case OP_greaterequals:
        {
          replace(1, InstructionFactory.createModifiedInstruction(OP_ifge, i.getInstruction()));
          break;
        }

      case OP_not:
        {
          InstructionInfo prev2 = previous(2);
          switch (prev2.getOpcode()) {
            case OP_strictequals:
              {
                replace(
                    2,
                    InstructionFactory.createModifiedInstruction(
                        OP_ifstrictne, i.getInstruction()));
                break;
              }
            case OP_equals:
              {
                replace(
                    2, InstructionFactory.createModifiedInstruction(OP_ifne, i.getInstruction()));
                break;
              }
            case OP_lessthan:
              {
                replace(
                    2, InstructionFactory.createModifiedInstruction(OP_ifnlt, i.getInstruction()));
                break;
              }
            case OP_lessequals:
              {
                replace(
                    2, InstructionFactory.createModifiedInstruction(OP_ifnle, i.getInstruction()));
                break;
              }
            case OP_greaterthan:
              {
                replace(
                    2, InstructionFactory.createModifiedInstruction(OP_ifngt, i.getInstruction()));
                break;
              }
            case OP_greaterequals:
              {
                replace(
                    2, InstructionFactory.createModifiedInstruction(OP_ifnge, i.getInstruction()));
                break;
              }
            default:
              {
                replace(
                    1,
                    InstructionFactory.createModifiedInstruction(OP_iffalse, i.getInstruction()));
                break;
              }
          }
          break;
        }

      default:
        // nothing to do, instruction was already added
    }
  }
  /**
   * Optimizations for OP_iffalse: convert_b, iffalse -> iffalse equals, iffalse -> ifne
   * strictequals, iffalse -> strictne lessthen, iffalse -> ifnlt lessequals, iffalse -> ifnle
   * greaterthan, iffalse -> ifngt greaterequals, iffalse -> ifnge pushfalse, iffalse -> jump
   * pushtrue, iffalse -> nothing strictequals, not, iffalse -> ifstrictequals equals, not, iffalse
   * -> ifeq lessthan, not, iffalse -> iflt lessequals, not, iffalse -> ifle greaterthan, not,
   * iffalse -> ifgt greaterequals, not,iffalse -> ifge not, iffalse -> iftrue
   *
   * @param i the iffalse instruction
   */
  private void op_iffalse(InstructionInfo i) {
    InstructionInfo prev = previous(1);

    // Check if we know what's on the stack. If so, we don't need
    // to do a conditional branch
    switch (isTrueInstructionInfo(prev)) {
      case TRUE:
        delete(1); // if we can't branch, just eat the push / iffalse instructions
        return;
      case FALSE:
        replace(1, InstructionFactory.createModifiedInstruction(OP_jump, i.getInstruction()));
        // If we always branch, then replace the push / iffalse with an uncontidional jump
        return;
      case DONT_KNOW:
        break; // continue on if we don't know it's a constant boolean
      default:
        assert false;
    }
    switch (prev.getOpcode()) {
      case OP_convert_b:
        {
          // replace with just iffalse
          replace(1, i.getInstruction());
          break;
        }
      case OP_equals:
        {
          replace(1, InstructionFactory.createModifiedInstruction(OP_ifne, i.getInstruction()));
          break;
        }
      case OP_strictequals:
        {
          replace(
              1, InstructionFactory.createModifiedInstruction(OP_ifstrictne, i.getInstruction()));
          break;
        }
      case OP_lessthan:
        {
          replace(1, InstructionFactory.createModifiedInstruction(OP_ifnlt, i.getInstruction()));
          break;
        }
      case OP_lessequals:
        {
          replace(1, InstructionFactory.createModifiedInstruction(OP_ifnle, i.getInstruction()));
          break;
        }
      case OP_greaterthan:
        {
          replace(1, InstructionFactory.createModifiedInstruction(OP_ifngt, i.getInstruction()));
          break;
        }
      case OP_greaterequals:
        {
          replace(1, InstructionFactory.createModifiedInstruction(OP_ifnge, i.getInstruction()));
          break;
        }

      case OP_not:
        {
          InstructionInfo prev2 = previous(2);
          switch (prev2.getOpcode()) {
            case OP_strictequals:
              {
                replace(
                    2,
                    InstructionFactory.createModifiedInstruction(
                        OP_ifstricteq, i.getInstruction()));
                break;
              }
            case OP_equals:
              {
                replace(
                    2, InstructionFactory.createModifiedInstruction(OP_ifeq, i.getInstruction()));
                break;
              }
            case OP_lessthan:
              {
                replace(
                    2, InstructionFactory.createModifiedInstruction(OP_iflt, i.getInstruction()));
                break;
              }
            case OP_lessequals:
              {
                replace(
                    2, InstructionFactory.createModifiedInstruction(OP_ifle, i.getInstruction()));
                break;
              }
            case OP_greaterthan:
              {
                replace(
                    2, InstructionFactory.createModifiedInstruction(OP_ifgt, i.getInstruction()));
                break;
              }
            case OP_greaterequals:
              {
                replace(
                    2, InstructionFactory.createModifiedInstruction(OP_ifge, i.getInstruction()));
                break;
              }
            default:
              {
                replace(
                    1, InstructionFactory.createModifiedInstruction(OP_iftrue, i.getInstruction()));
                break;
              }
          }
          break;
        }

      default:
        {
          // nothing to do, instruction was already added
        }
    }
  }
  /**
   * Helper method to invert if expr target jump other-target to if not expr other-target Used to
   * optimize some common patterns with if's and jumps
   */
  private Instruction invertIf(InstructionInfo oldIf, InstructionInfo oldJump) {
    Instruction newIf = null;
    switch (oldIf.getOpcode()) {
      case OP_ifeq:
        {
          newIf = InstructionFactory.createModifiedInstruction(OP_ifne, oldJump.getInstruction());
          break;
        }
      case OP_ifstricteq:
        {
          newIf =
              InstructionFactory.createModifiedInstruction(OP_ifstrictne, oldJump.getInstruction());
          break;
        }
      case OP_ifstrictne:
        {
          newIf =
              InstructionFactory.createModifiedInstruction(OP_ifstricteq, oldJump.getInstruction());
          break;
        }
      case OP_ifge:
        {
          newIf = InstructionFactory.createModifiedInstruction(OP_ifnge, oldJump.getInstruction());
          break;
        }
      case OP_ifgt:
        {
          newIf = InstructionFactory.createModifiedInstruction(OP_ifngt, oldJump.getInstruction());
          break;
        }
      case OP_iflt:
        {
          newIf = InstructionFactory.createModifiedInstruction(OP_ifnlt, oldJump.getInstruction());
          break;
        }
      case OP_ifle:
        {
          newIf = InstructionFactory.createModifiedInstruction(OP_ifnle, oldJump.getInstruction());
          break;
        }
      case OP_ifne:
        {
          newIf = InstructionFactory.createModifiedInstruction(OP_ifeq, oldJump.getInstruction());
          break;
        }
      case OP_ifnge:
        {
          newIf = InstructionFactory.createModifiedInstruction(OP_ifge, oldJump.getInstruction());
          break;
        }
      case OP_ifngt:
        {
          newIf = InstructionFactory.createModifiedInstruction(OP_ifgt, oldJump.getInstruction());
          break;
        }
      case OP_ifnlt:
        {
          newIf = InstructionFactory.createModifiedInstruction(OP_iflt, oldJump.getInstruction());
          break;
        }
      case OP_ifnle:
        {
          newIf = InstructionFactory.createModifiedInstruction(OP_ifle, oldJump.getInstruction());
          break;
        }
      case OP_iftrue:
        {
          newIf =
              InstructionFactory.createModifiedInstruction(OP_iffalse, oldJump.getInstruction());
          break;
        }
      case OP_iffalse:
        {
          newIf = InstructionFactory.createModifiedInstruction(OP_iftrue, oldJump.getInstruction());
          break;
        }
    }

    return newIf;
  }
  /**
   * Perform optimizations on the instructions currently held in the window. This is so we don't do
   * the optimizations until we are sure a particular instruction was or was not the target of a
   * jump. If it was a target, then we don't do the optimizations as we don't have enough
   * information to determine all the ways control might flow to that instruction.
   */
  private void processPreviousInstructions() {
    InstructionInfo last = previous(0);
    InstructionInfo secondLast = previous(1);

    // Check if we have a labelCurrent on the last instruction, or a labelNext on the previous
    // instruction
    if (last.getLabelCurrents().isEmpty() && secondLast.getLabelNexts().isEmpty()) {
      switch (last.getOpcode()) {
        case OP_convert_b:
          {
            op_convert_b(last);
            break;
          }
        case OP_convert_d:
          {
            op_convert_d(last);
            break;
          }
        case OP_convert_i:
          {
            op_convert_i(last);
            break;
          }
        case OP_convert_u:
          {
            op_convert_u(last);
            break;
          }
        case OP_convert_s:
          {
            op_convert_s(last);
            break;
          }
        case OP_getproperty:
          {
            op_getproperty(last);
            break;
          }
        case OP_iffalse:
          {
            op_iffalse(last);
            break;
          }
        case OP_iftrue:
          {
            op_iftrue(last);
            break;
          }
        case OP_pop:
          {
            op_pop(last);
            break;
          }
        case OP_nop:
          {
            delete(0);
            break;
          }
        case OP_getlocal:
          {
            OP_getlocal(last);
            break;
          }
        case OP_returnvoid:
          {
            op_returnvoid(last);
            break;
          }
        default:
          {
            break;
          }
      }
    }
  }