/*
 *  This file is part of the Jikes RVM project (http://jikesrvm.org).
 *
 *  This file is licensed to You under the Common Public License (CPL);
 *  You may not use this file except in compliance with the License. You
 *  may obtain a copy of the License at
 *
 *      http://www.opensource.org/licenses/cpl1.0.php
 *
 *  See the COPYRIGHT.txt file distributed with this work for information
 *  regarding copyright ownership.
 */
package org.jikesrvm.compilers.opt.hir2lir;

import static org.jikesrvm.SizeConstants.LOG_BYTES_IN_ADDRESS;
import static org.jikesrvm.SizeConstants.LOG_BYTES_IN_INT;
import static org.jikesrvm.compilers.opt.driver.OptConstants.RUNTIME_SERVICES_BCI;
import static org.jikesrvm.compilers.opt.ir.Operators.ARRAYLENGTH;
import static org.jikesrvm.compilers.opt.ir.Operators.BOUNDS_CHECK_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.BYTE_ALOAD_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.BYTE_ASTORE_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.BYTE_LOAD;
import static org.jikesrvm.compilers.opt.ir.Operators.BYTE_STORE;
import static org.jikesrvm.compilers.opt.ir.Operators.CALL;
import static org.jikesrvm.compilers.opt.ir.Operators.CALL_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.CHECKCAST_NOTNULL_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.CHECKCAST_UNRESOLVED_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.CHECKCAST_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.DOUBLE_ALOAD_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.DOUBLE_ASTORE_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.DOUBLE_LOAD;
import static org.jikesrvm.compilers.opt.ir.Operators.DOUBLE_STORE;
import static org.jikesrvm.compilers.opt.ir.Operators.FLOAT_ALOAD_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.FLOAT_ASTORE_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.FLOAT_LOAD;
import static org.jikesrvm.compilers.opt.ir.Operators.FLOAT_STORE;
import static org.jikesrvm.compilers.opt.ir.Operators.GETFIELD_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.GETSTATIC_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.GET_CLASS_TIB;
import static org.jikesrvm.compilers.opt.ir.Operators.GET_OBJ_TIB;
import static org.jikesrvm.compilers.opt.ir.Operators.GOTO;
import static org.jikesrvm.compilers.opt.ir.Operators.IG_CLASS_TEST_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.IG_METHOD_TEST_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.INSTANCEOF_NOTNULL_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.INSTANCEOF_UNRESOLVED_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.INSTANCEOF_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.INT_2ADDRSigExt;
import static org.jikesrvm.compilers.opt.ir.Operators.INT_2ADDRZerExt;
import static org.jikesrvm.compilers.opt.ir.Operators.INT_ADD;
import static org.jikesrvm.compilers.opt.ir.Operators.INT_ALOAD_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.INT_ASTORE_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.INT_IFCMP;
import static org.jikesrvm.compilers.opt.ir.Operators.INT_IFCMP2;
import static org.jikesrvm.compilers.opt.ir.Operators.INT_LOAD;
import static org.jikesrvm.compilers.opt.ir.Operators.INT_SHL;
import static org.jikesrvm.compilers.opt.ir.Operators.INT_STORE;
import static org.jikesrvm.compilers.opt.ir.Operators.INT_ZERO_CHECK_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.LONG_ALOAD_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.LONG_ASTORE_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.LONG_LOAD;
import static org.jikesrvm.compilers.opt.ir.Operators.LONG_STORE;
import static org.jikesrvm.compilers.opt.ir.Operators.LONG_ZERO_CHECK_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.LOOKUPSWITCH;
import static org.jikesrvm.compilers.opt.ir.Operators.LOOKUPSWITCH_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.LOWTABLESWITCH;
import static org.jikesrvm.compilers.opt.ir.Operators.MUST_IMPLEMENT_INTERFACE_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.OBJARRAY_STORE_CHECK_NOTNULL_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.OBJARRAY_STORE_CHECK_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.PUTFIELD_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.PUTSTATIC_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.REF_ALOAD_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.REF_ASTORE_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.REF_IFCMP;
import static org.jikesrvm.compilers.opt.ir.Operators.REF_LOAD;
import static org.jikesrvm.compilers.opt.ir.Operators.REF_STORE;
import static org.jikesrvm.compilers.opt.ir.Operators.RESOLVE;
import static org.jikesrvm.compilers.opt.ir.Operators.RESOLVE_MEMBER_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.SHORT_ALOAD_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.SHORT_ASTORE_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.SHORT_LOAD;
import static org.jikesrvm.compilers.opt.ir.Operators.SHORT_STORE;
import static org.jikesrvm.compilers.opt.ir.Operators.SYSCALL_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.TABLESWITCH_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.TRAP_IF;
import static org.jikesrvm.compilers.opt.ir.Operators.UBYTE_ALOAD_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.UBYTE_LOAD;
import static org.jikesrvm.compilers.opt.ir.Operators.USHORT_ALOAD_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.USHORT_LOAD;
import static org.jikesrvm.objectmodel.TIBLayoutConstants.NEEDS_DYNAMIC_LINK;
import static org.jikesrvm.objectmodel.TIBLayoutConstants.TIB_INTERFACE_DISPATCH_TABLE_INDEX;

import org.jikesrvm.VM;
import org.jikesrvm.adaptive.AosEntrypoints;
import org.jikesrvm.classloader.RVMClass;
import org.jikesrvm.classloader.RVMField;
import org.jikesrvm.classloader.InterfaceInvocation;
import org.jikesrvm.classloader.InterfaceMethodSignature;
import org.jikesrvm.classloader.RVMMethod;
import org.jikesrvm.classloader.RVMType;
import org.jikesrvm.classloader.TypeReference;
import org.jikesrvm.compilers.opt.OptOptions;
import org.jikesrvm.compilers.opt.controlflow.BranchOptimizations;
import org.jikesrvm.compilers.opt.ir.ALoad;
import org.jikesrvm.compilers.opt.ir.AStore;
import org.jikesrvm.compilers.opt.ir.BasicBlock;
import org.jikesrvm.compilers.opt.ir.Binary;
import org.jikesrvm.compilers.opt.ir.BoundsCheck;
import org.jikesrvm.compilers.opt.ir.CacheOp;
import org.jikesrvm.compilers.opt.ir.Call;
import org.jikesrvm.compilers.opt.ir.GetField;
import org.jikesrvm.compilers.opt.ir.GetStatic;
import org.jikesrvm.compilers.opt.ir.Goto;
import org.jikesrvm.compilers.opt.ir.GuardedUnary;
import org.jikesrvm.compilers.opt.ir.IR;
import org.jikesrvm.compilers.opt.ir.IRTools;
import org.jikesrvm.compilers.opt.ir.IfCmp;
import org.jikesrvm.compilers.opt.ir.IfCmp2;
import org.jikesrvm.compilers.opt.ir.InlineGuard;
import org.jikesrvm.compilers.opt.ir.Instruction;
import org.jikesrvm.compilers.opt.ir.Load;
import org.jikesrvm.compilers.opt.ir.LookupSwitch;
import org.jikesrvm.compilers.opt.ir.LowTableSwitch;
import org.jikesrvm.compilers.opt.ir.Operator;
import org.jikesrvm.compilers.opt.ir.PutField;
import org.jikesrvm.compilers.opt.ir.PutStatic;
import org.jikesrvm.compilers.opt.ir.Store;
import org.jikesrvm.compilers.opt.ir.TableSwitch;
import org.jikesrvm.compilers.opt.ir.TrapIf;
import org.jikesrvm.compilers.opt.ir.Unary;
import org.jikesrvm.compilers.opt.ir.ZeroCheck;
import org.jikesrvm.compilers.opt.ir.operand.AddressConstantOperand;
import org.jikesrvm.compilers.opt.ir.operand.BranchOperand;
import org.jikesrvm.compilers.opt.ir.operand.BranchProfileOperand;
import org.jikesrvm.compilers.opt.ir.operand.ConditionOperand;
import org.jikesrvm.compilers.opt.ir.operand.IntConstantOperand;
import org.jikesrvm.compilers.opt.ir.operand.LocationOperand;
import org.jikesrvm.compilers.opt.ir.operand.MethodOperand;
import org.jikesrvm.compilers.opt.ir.operand.Operand;
import org.jikesrvm.compilers.opt.ir.operand.RegisterOperand;
import org.jikesrvm.compilers.opt.ir.operand.TIBConstantOperand;
import org.jikesrvm.compilers.opt.ir.operand.TrapCodeOperand;
import org.jikesrvm.compilers.opt.ir.operand.TypeOperand;
import org.jikesrvm.compilers.opt.specialization.SpecializedMethod;
import org.jikesrvm.mm.mminterface.MemoryManagerConstants;
import org.jikesrvm.runtime.Entrypoints;
import org.jikesrvm.runtime.Magic;
import org.vmmagic.unboxed.Address;
import org.vmmagic.unboxed.Offset;

/**
 * Converts all remaining instructions with HIR-only operators into
 * an equivalent sequence of LIR operators.
 */
public abstract class ConvertToLowLevelIR extends IRTools {

  /**
   * We have slightly different ideas of what the LIR should look like
   * for IA32 and PowerPC.  The main difference is that for IA32
   * instead of bending over backwards in BURS to rediscover array
   * loads, (where we can use base + index*scale addressing modes),
   * we'll leave array loads in the LIR.
   */
  public static final boolean LOWER_ARRAY_ACCESS = VM.BuildForPowerPC;
  /**
   * Plant virtual calls via the JTOC rather than from the tib of an
   * object when possible
   */
  public static final boolean CALL_VIA_JTOC = false;

  /**
   * Converts the given HIR to LIR.
   *
   * @param ir IR to convert
   */
  static void convert(IR ir, OptOptions options) {
    boolean didArrayStoreCheck = false;
    for (Instruction s = ir.firstInstructionInCodeOrder(); s != null; s = s.nextInstructionInCodeOrder()) {

      switch (s.getOpcode()) {
        case GETSTATIC_opcode: {
          LocationOperand loc = GetStatic.getClearLocation(s);
          RegisterOperand result = GetStatic.getClearResult(s);
          Operand address = ir.regpool.makeJTOCOp(ir, s);
          Operand offset = GetStatic.getClearOffset(s);
          Load.mutate(s, IRTools.getLoadOp(loc.getFieldRef(), true), result, address, offset, loc);
        }
        break;

        case PUTSTATIC_opcode: {
          LocationOperand loc = PutStatic.getClearLocation(s);
          Operand value = PutStatic.getClearValue(s);
          Operand address = ir.regpool.makeJTOCOp(ir, s);
          Operand offset = PutStatic.getClearOffset(s);
          Store.mutate(s, IRTools.getStoreOp(loc.getFieldRef(), true), value, address, offset, loc);
        }
        break;

        case PUTFIELD_opcode: {
          LocationOperand loc = PutField.getClearLocation(s);
          Operand value = PutField.getClearValue(s);
          Operand address = PutField.getClearRef(s);
          Operand offset = PutField.getClearOffset(s);
          Store.mutate(s,
                       IRTools.getStoreOp(loc.getFieldRef(), false),
                       value,
                       address,
                       offset,
                       loc,
                       PutField.getClearGuard(s));
        }
        break;

        case GETFIELD_opcode: {
          LocationOperand loc = GetField.getClearLocation(s);
          RegisterOperand result = GetField.getClearResult(s);
          Operand address = GetField.getClearRef(s);
          Operand offset = GetField.getClearOffset(s);
          Load.mutate(s,
                      IRTools.getLoadOp(loc.getFieldRef(), false),
                      result,
                      address,
                      offset,
                      loc,
                      GetField.getClearGuard(s));
        }
        break;

        case INT_ALOAD_opcode:
          doArrayLoad(s, ir, INT_LOAD, 2);
          break;

        case LONG_ALOAD_opcode:
          doArrayLoad(s, ir, LONG_LOAD, 3);
          break;

        case FLOAT_ALOAD_opcode:
          doArrayLoad(s, ir, FLOAT_LOAD, 2);
          break;

        case DOUBLE_ALOAD_opcode:
          doArrayLoad(s, ir, DOUBLE_LOAD, 3);
          break;

        case REF_ALOAD_opcode:
          doArrayLoad(s, ir, REF_LOAD, LOG_BYTES_IN_ADDRESS);
          break;

        case BYTE_ALOAD_opcode:
          doArrayLoad(s, ir, BYTE_LOAD, 0);
          break;

        case UBYTE_ALOAD_opcode:
          doArrayLoad(s, ir, UBYTE_LOAD, 0);
          break;

        case USHORT_ALOAD_opcode:
          doArrayLoad(s, ir, USHORT_LOAD, 1);
          break;

        case SHORT_ALOAD_opcode:
          doArrayLoad(s, ir, SHORT_LOAD, 1);
          break;

        case INT_ASTORE_opcode:
          doArrayStore(s, ir, INT_STORE, 2);
          break;

        case LONG_ASTORE_opcode:
          doArrayStore(s, ir, LONG_STORE, 3);
          break;

        case FLOAT_ASTORE_opcode:
          doArrayStore(s, ir, FLOAT_STORE, 2);
          break;

        case DOUBLE_ASTORE_opcode:
          doArrayStore(s, ir, DOUBLE_STORE, 3);
          break;

        case REF_ASTORE_opcode:
          doArrayStore(s, ir, REF_STORE, LOG_BYTES_IN_ADDRESS);
          break;

        case BYTE_ASTORE_opcode:
          doArrayStore(s, ir, BYTE_STORE, 0);
          break;

        case SHORT_ASTORE_opcode:
          doArrayStore(s, ir, SHORT_STORE, 1);
          break;

        case CALL_opcode:
          s = callHelper(s, ir);
          break;

        case SYSCALL_opcode:
          // If the SYSCALL is using a symbolic address, convert that to
          // a sequence of loads off the BootRecord to find the appropriate field.
          if (Call.getMethod(s) != null) {
            expandSysCallTarget(s, ir);
          }
          break;

        case TABLESWITCH_opcode:
          s = tableswitch(s, ir);
          break;

        case LOOKUPSWITCH_opcode:
          s = lookup(s, ir);
          break;

        case OBJARRAY_STORE_CHECK_opcode:
          s = DynamicTypeCheckExpansion.arrayStoreCheck(s, ir, true);
          didArrayStoreCheck = true;
          break;

        case OBJARRAY_STORE_CHECK_NOTNULL_opcode:
          s = DynamicTypeCheckExpansion.arrayStoreCheck(s, ir, false);
          didArrayStoreCheck = true;
          break;

        case CHECKCAST_opcode:
        case CHECKCAST_UNRESOLVED_opcode:
          s = DynamicTypeCheckExpansion.checkcast(s, ir);
          break;

        case CHECKCAST_NOTNULL_opcode:
          s = DynamicTypeCheckExpansion.checkcastNotNull(s, ir);
          break;

        case MUST_IMPLEMENT_INTERFACE_opcode:
          s = DynamicTypeCheckExpansion.mustImplementInterface(s, ir);
          break;

        case IG_CLASS_TEST_opcode:
          IfCmp.mutate(s,
                       REF_IFCMP,
                       ir.regpool.makeTempValidation(),
                       getTIB(s, ir, InlineGuard.getClearValue(s), InlineGuard.getClearGuard(s)),
                       getTIB(s, ir, InlineGuard.getGoal(s).asType()),
                       ConditionOperand.NOT_EQUAL(),
                       InlineGuard.getClearTarget(s),
                       InlineGuard.getClearBranchProfile(s));
          break;

        case IG_METHOD_TEST_opcode: {
          MethodOperand methOp = InlineGuard.getClearGoal(s).asMethod();
          Operand t1 = getTIB(s, ir, InlineGuard.getClearValue(s), InlineGuard.getClearGuard(s));
          Operand t2 = getTIB(s, ir, methOp.getTarget().getDeclaringClass());
          IfCmp.mutate(s,
                       REF_IFCMP,
                       ir.regpool.makeTempValidation(),
                       getInstanceMethod(s, ir, t1, methOp.getTarget()),
                       getInstanceMethod(s, ir, t2, methOp.getTarget()),
                       ConditionOperand.NOT_EQUAL(),
                       InlineGuard.getClearTarget(s),
                       InlineGuard.getClearBranchProfile(s));
          break;
        }

        case INSTANCEOF_opcode:
        case INSTANCEOF_UNRESOLVED_opcode:
          s = DynamicTypeCheckExpansion.instanceOf(s, ir);
          break;

        case INSTANCEOF_NOTNULL_opcode:
          s = DynamicTypeCheckExpansion.instanceOfNotNull(s, ir);
          break;

        case INT_ZERO_CHECK_opcode: {
          TrapIf.mutate(s,
                        TRAP_IF,
                        ZeroCheck.getClearGuardResult(s),
                        ZeroCheck.getClearValue(s),
                        IC(0),
                        ConditionOperand.EQUAL(),
                        TrapCodeOperand.DivByZero());
        }
        break;

        case LONG_ZERO_CHECK_opcode: {
          TrapIf.mutate(s,
                        TRAP_IF,
                        ZeroCheck.getClearGuardResult(s),
                        ZeroCheck.getClearValue(s),
                        LC(0),
                        ConditionOperand.EQUAL(),
                        TrapCodeOperand.DivByZero());
        }
        break;

        case BOUNDS_CHECK_opcode: {
          // get array_length from array_ref
          RegisterOperand array_length =
              InsertGuardedUnary(s,
                                 ir,
                                 ARRAYLENGTH,
                                 TypeReference.Int,
                                 BoundsCheck.getClearRef(s),
                                 BoundsCheck.getClearGuard(s));
          //  In UN-signed comparison, a negative index will look like a very
          //  large positive number, greater than array length.
          //  Thus length LLT index is false iff 0 <= index <= length
          TrapIf.mutate(s,
                        TRAP_IF,
                        BoundsCheck.getClearGuardResult(s),
                        array_length.copyD2U(),
                        BoundsCheck.getClearIndex(s),
                        ConditionOperand.LOWER_EQUAL(),
                        TrapCodeOperand.ArrayBounds());
        }
        break;

        case RESOLVE_MEMBER_opcode:
          s = resolveMember(s, ir);
          break;

        default:
          break;
      }
    }
    // Eliminate possible redundant trap block from array store checks
    if (didArrayStoreCheck) {
      branchOpts.perform(ir, true);
    }
  }

  private static BranchOptimizations branchOpts = new BranchOptimizations(-1, true, true);

  /**
   * Expand a tableswitch.
   * @param s the instruction to expand
   * @param ir the containing IR
   * @return the last Instruction in the generated LIR sequence.
   */
  static Instruction tableswitch(Instruction s, IR ir) {

    Instruction s2;
    int lowLimit = TableSwitch.getLow(s).value;
    int highLimit = TableSwitch.getHigh(s).value;
    int number = highLimit - lowLimit + 1;
    if (VM.VerifyAssertions) {
      VM._assert(number > 0);    // also checks that there are < 2^31 targets
    }
    Operand val = TableSwitch.getClearValue(s);
    BranchOperand defaultLabel = TableSwitch.getClearDefault(s);
    if (number < 8) {           // convert into a lookupswitch
      Instruction l =
          LookupSwitch.create(LOOKUPSWITCH,
                              val,
                              null,
                              null,
                              defaultLabel,
                              TableSwitch.getClearDefaultBranchProfile(s),
                              number * 3);
      for (int i = 0; i < number; i++) {
        LookupSwitch.setMatch(l, i, IC(lowLimit + i));
        LookupSwitch.setTarget(l, i, TableSwitch.getClearTarget(s, i));
        LookupSwitch.setBranchProfile(l, i, TableSwitch.getClearBranchProfile(s, i));
      }
      s.insertAfter(l);
      return s.remove();
    }
    RegisterOperand reg = val.asRegister();
    BasicBlock BB1 = s.getBasicBlock();
    BasicBlock BB2 = BB1.splitNodeAt(s, ir);
    BasicBlock defaultBB = defaultLabel.target.getBasicBlock();

    /******* First basic block */
    RegisterOperand t;
    if (lowLimit != 0) {
      t = insertBinary(s, ir, INT_ADD, TypeReference.Int, reg, IC(-lowLimit));
    } else {
      t = reg.copyU2U();
    }
    BranchProfileOperand defaultProb = TableSwitch.getClearDefaultBranchProfile(s);
    s.replace(IfCmp.create(INT_IFCMP,
        ir.regpool.makeTempValidation(),
        t,
        IC(highLimit - lowLimit),
        ConditionOperand.HIGHER(),
        defaultLabel,
        defaultProb));
    // Reweight branches to account for the default branch going. If
    // the default probability was ALWAYS then when we recompute the
    // weight to be a proportion of the total number of branches.
    final boolean defaultIsAlways = defaultProb.takenProbability >= 1f;
    final float weight = defaultIsAlways ? 1f / number : 1f / (1f - defaultProb.takenProbability);

    /********** second Basic Block ******/
    s2 = LowTableSwitch.create(LOWTABLESWITCH, t.copyRO(), number * 2);
    boolean containsDefault = false;
    for (int i = 0; i < number; i++) {
      BranchOperand b = TableSwitch.getClearTarget(s, i);
      LowTableSwitch.setTarget(s2, i, b);
      BranchProfileOperand bp = TableSwitch.getClearBranchProfile(s, i);
      if (defaultIsAlways) {
        bp.takenProbability = weight;
      } else {
        bp.takenProbability *= weight;
      }
      LowTableSwitch.setBranchProfile(s2, i, bp);
      if (b.target == defaultLabel.target) {
        containsDefault = true;
      }
    }
    // Fixup the CFG and code order.
    BB1.insertOut(BB2);
    BB1.insertOut(defaultBB);
    ir.cfg.linkInCodeOrder(BB1, BB2);
    if (!containsDefault) {
      BB2.deleteOut(defaultBB);
    }
    // Simplify a fringe case...
    // if all targets of the LOWTABLESWITCH are the same,
    // then just use a GOTO instead of the LOWTABLESWITCH.
    // This actually happens (very occasionally), and is easy to test for.
    if (BB2.getNumberOfNormalOut() == 1) {
      BB2.appendInstruction(Goto.create(GOTO, LowTableSwitch.getTarget(s2, 0)));
    } else {
      BB2.appendInstruction(s2);
    }
    // continue at next BB
    s = BB2.lastInstruction();

    return s;
  }

  /**
   * Expand a lookupswitch.
   * @param switchInstr  The instruction to expand
   * @param ir           The containing IR
   * @return the next {@link Instruction} after the generated LIR sequence.
   */
  static Instruction lookup(Instruction switchInstr, IR ir) {
    Instruction bbend = switchInstr.nextInstructionInCodeOrder();
    BasicBlock thisBB = bbend.getBasicBlock();
    BasicBlock nextBB = thisBB.nextBasicBlockInCodeOrder();
    // Blow away the old Normal ControlFlowGraph edges to prepare for new links
    thisBB.deleteNormalOut();
    switchInstr.remove();
    BranchOperand defTarget = LookupSwitch.getClearDefault(switchInstr);
    BasicBlock defaultBB = defTarget.target.getBasicBlock();
    int high = LookupSwitch.getNumberOfTargets(switchInstr) - 1;
    if (high < 0) {
      // no cases in switch; just jump to defaultBB
      thisBB.appendInstruction(Goto.create(GOTO, defTarget));
      thisBB.insertOut(defaultBB);
    } else {
      Operand match = LookupSwitch.getValue(switchInstr);
      if (match.isConstant()) {
        // switch on a constant
        int value = match.asIntConstant().value;
        int numMatches = LookupSwitch.getNumberOfMatches(switchInstr);
        BranchOperand target = LookupSwitch.getDefault(switchInstr);
        for (int i = 0; i < numMatches; i++) {
          if (value == LookupSwitch.getMatch(switchInstr, i).value) {
            target = LookupSwitch.getTarget(switchInstr, i);
            break;
          }
        }
        thisBB.appendInstruction(Goto.create(GOTO, target));
        thisBB.insertOut(target.target.getBasicBlock());
      } else {
        RegisterOperand reg = match.asRegister();

        // If you're not already at the end of the code order
        if (nextBB != null) {
          ir.cfg.breakCodeOrder(thisBB, nextBB);
        }
        // generate the binary search tree into thisBB
        BasicBlock lastNewBB =
            _lookupswitchHelper(switchInstr, reg, defaultBB, ir, thisBB, 0, high, Integer.MIN_VALUE, Integer.MAX_VALUE);
        if (nextBB != null) {
          ir.cfg.linkInCodeOrder(lastNewBB, nextBB);
        }
      }
    }

    // skip all the instrs just inserted by _lookupswitchHelper
    if (nextBB != null) {
      return nextBB.firstInstruction();
    } else {
      return thisBB.lastInstruction();
    }
  }

  /**
   * Helper function to generate the binary search tree for
   * a lookupswitch bytecode
   *
   * @param switchInstr the lookupswitch instruction
   * @param defaultBB the basic block of the default case
   * @param ir the ir object
   * @param curBlock the basic block to insert instructions into
   * @param reg the RegisterOperand that contains the valued being switched on
   * @param low the low index of cases (operands of switchInstr)
   * @param high the high index of cases (operands of switchInstr)
   * @param min
   * @param max
   * @return the last basic block created
   */
  private static BasicBlock _lookupswitchHelper(Instruction switchInstr, RegisterOperand reg,
                                                    BasicBlock defaultBB, IR ir, BasicBlock curBlock,
                                                    int low, int high, int min, int max) {
    if (VM.VerifyAssertions) {
      VM._assert(low <= high, "broken control logic in _lookupswitchHelper");
    }

    int middle = (low + high) >> 1;             // find middle

    // The following are used below to store the computed branch
    // probabilities for the branches that are created to implement
    // the binary search.  Used only if basic block frequencies available
    float lessProb = 0.0f;
    float greaterProb = 0.0f;
    float equalProb = 0.0f;
    float sum = 0.0f;

    // Sum the probabilities for all targets < middle
    for (int i = low; i < middle; i++) {
      lessProb += LookupSwitch.getBranchProfile(switchInstr, i).takenProbability;
    }

    // Sum the probabilities for all targets > middle
    for (int i = middle + 1; i <= high; i++) {
      greaterProb += LookupSwitch.getBranchProfile(switchInstr, i).takenProbability;
    }
    equalProb = LookupSwitch.getBranchProfile(switchInstr, middle).takenProbability;

    // The default case is a bit of a kludge.  We know the total
    // probability of executing the default case, but we have no
    // idea which paths are taken to get there.  For now, we'll
    // assume that all paths that went to default were because the
    // value was less than the smallest switch value.  This ensures
    // that all basic block appearing in the switch will have the
    // correct weights (but the blocks in the binary switch
    // generated may not).
    if (low == 0) {
      lessProb += LookupSwitch.getDefaultBranchProfile(switchInstr).takenProbability;
    }

    // Now normalize them so they are relative to the sum of the
    // branches being considered in this piece of the subtree
    sum = lessProb + equalProb + greaterProb;
    if (sum > 0) {  // check for divide by zero
      lessProb /= sum;
      equalProb /= sum;
      greaterProb /= sum;
    }

    IntConstantOperand val = LookupSwitch.getClearMatch(switchInstr, middle);
    int value = val.value;
    BasicBlock greaterBlock = middle == high ? defaultBB : curBlock.createSubBlock(0, ir);
    BasicBlock lesserBlock = low == middle ? defaultBB : curBlock.createSubBlock(0, ir);
    // Generate this level of tests
    BranchOperand branch = LookupSwitch.getClearTarget(switchInstr, middle);
    BasicBlock branchBB = branch.target.getBasicBlock();
    curBlock.insertOut(branchBB);
    if (low != high) {
      if (value == min) {
        curBlock.appendInstruction(IfCmp.create(INT_IFCMP,
            ir.regpool.makeTempValidation(),
            reg.copy(),
            val,
            ConditionOperand.EQUAL(),
            branchBB.makeJumpTarget(),
            new BranchProfileOperand(equalProb)));
      } else {

        // To compute the probability of the second compare, the first
        // probability must be removed since the second branch is
        // considered only if the first fails.
        float secondIfProb = 0.0f;
        sum = equalProb + greaterProb;
        if (sum > 0) {
          // if divide by zero, leave as is
          secondIfProb = equalProb / sum;
        }

        curBlock.appendInstruction(IfCmp2.create(INT_IFCMP2,
            ir.regpool.makeTempValidation(),
            reg.copy(),
            val,
            ConditionOperand.LESS(),
            lesserBlock.makeJumpTarget(),
            new BranchProfileOperand(lessProb),
            ConditionOperand.EQUAL(),
            branchBB.makeJumpTarget(),
            new BranchProfileOperand(secondIfProb)));
        curBlock.insertOut(lesserBlock);
      }
    } else {      // Base case: middle was the only case left to consider
      if (min == max) {
        curBlock.appendInstruction(Goto.create(GOTO, branch));
        curBlock.insertOut(branchBB);
      } else {
        curBlock.appendInstruction(IfCmp.create(INT_IFCMP,
            ir.regpool.makeTempValidation(),
            reg.copy(),
            val,
            ConditionOperand.EQUAL(),
            branchBB.makeJumpTarget(),
            new BranchProfileOperand(equalProb)));
        BasicBlock newBlock = curBlock.createSubBlock(0, ir);
        curBlock.insertOut(newBlock);
        ir.cfg.linkInCodeOrder(curBlock, newBlock);
        curBlock = newBlock;
        curBlock.appendInstruction(defaultBB.makeGOTO());
        curBlock.insertOut(defaultBB);
      }
    }
    // Generate sublevels as needed and splice together instr & bblist
    if (middle < high) {
      curBlock.insertOut(greaterBlock);
      ir.cfg.linkInCodeOrder(curBlock, greaterBlock);
      curBlock = _lookupswitchHelper(switchInstr, reg, defaultBB, ir, greaterBlock, middle + 1, high, value + 1, max);
    }
    if (low < middle) {
      ir.cfg.linkInCodeOrder(curBlock, lesserBlock);
      curBlock = _lookupswitchHelper(switchInstr, reg, defaultBB, ir, lesserBlock, low, middle - 1, min, value - 1);
    }
    return curBlock;
  }

  /**
   * Expand an array load.
   * @param s the instruction to expand
   * @param ir the containing IR
   * @param op the load operator to use
   * @param logwidth the log base 2 of the element type's size
   */
  public static void doArrayLoad(Instruction s, IR ir, Operator op, int logwidth) {
    if (LOWER_ARRAY_ACCESS) {
      RegisterOperand result = ALoad.getClearResult(s);
      Operand array = ALoad.getClearArray(s);
      Operand index = ALoad.getClearIndex(s);
      Operand offset;
      LocationOperand loc = ALoad.getClearLocation(s);
      if (index instanceof IntConstantOperand) {  // constant propagation
        offset = AC(Address.fromIntZeroExtend(((IntConstantOperand) index).value << logwidth));
      } else {
        if (logwidth != 0) {
          offset = insertBinary(s, ir, INT_SHL, TypeReference.Int, index, IC(logwidth));
          offset = InsertUnary(s, ir, INT_2ADDRZerExt, TypeReference.Offset, offset.copy());
        } else {
          offset = InsertUnary(s, ir, INT_2ADDRZerExt, TypeReference.Offset, index);
        }
      }
      Load.mutate(s, op, result, array, offset, loc, ALoad.getClearGuard(s));
    }
  }

  /**
   * Expand an array store.
   * @param s the instruction to expand
   * @param ir the containing IR
   * @param op the store operator to use
   * @param logwidth the log base 2 of the element type's size
   */
  public static void doArrayStore(Instruction s, IR ir, Operator op, int logwidth) {
    if (LOWER_ARRAY_ACCESS) {
      Operand value = AStore.getClearValue(s);
      Operand array = AStore.getClearArray(s);
      Operand index = AStore.getClearIndex(s);
      Operand offset;
      LocationOperand loc = AStore.getClearLocation(s);
      if (index instanceof IntConstantOperand) {// constant propagation
        offset = AC(Address.fromIntZeroExtend(((IntConstantOperand) index).value << logwidth));
      } else {
        if (logwidth != 0) {
          offset = insertBinary(s, ir, INT_SHL, TypeReference.Int, index, IC(logwidth));
          offset = InsertUnary(s, ir, INT_2ADDRZerExt, TypeReference.Offset, offset.copy());
        } else {
          offset = InsertUnary(s, ir, INT_2ADDRZerExt, TypeReference.Offset, index);
        }
      }
      Store.mutate(s, op, value, array, offset, loc, AStore.getClearGuard(s));
    }
  }

  /**
   * Helper method for call expansion.
   * @param v the call instruction
   * @param ir the containing IR
   * @return the last expanded instruction
   */
  static Instruction callHelper(Instruction v, IR ir) {
    if (!Call.hasMethod(v)) {
      if (VM.VerifyAssertions) VM._assert(Call.getAddress(v) instanceof RegisterOperand);
      return v; // nothing to do....very low level call to address already in the register.
    }

    MethodOperand methOp = Call.getMethod(v);

    // Handle recursive invocations.
    if (methOp.hasPreciseTarget() && methOp.getTarget() == ir.method) {
      Call.setAddress(v, new BranchOperand(ir.firstInstructionInCodeOrder()));
      return v;
    }

    /* RRB 100500 */
    // generate direct call to specialized method if the method operand
    // has been marked as a specialized call.
    if (VM.runningVM) {
      SpecializedMethod spMethod = methOp.spMethod;
      if (spMethod != null) {
        int smid = spMethod.getSpecializedMethodIndex();
        Call.setAddress(v, getSpecialMethod(v, ir, smid));
        return v;
      }
    }

    // Used mainly (only?) by OSR
    if (methOp.hasDesignatedTarget()) {
      Call.setAddress(v, InsertLoadOffsetJTOC(v, ir, REF_LOAD, TypeReference.CodeArray, methOp.jtocOffset));
      return v;
    }

    if (methOp.isStatic()) {
      if (VM.VerifyAssertions) VM._assert(Call.hasAddress(v));
      Call.setAddress(v, InsertLoadOffsetJTOC(v, ir, REF_LOAD, TypeReference.CodeArray, Call.getClearAddress(v)));
    } else if (methOp.isVirtual()) {
      if (VM.VerifyAssertions) VM._assert(Call.hasAddress(v));
      if (CALL_VIA_JTOC && methOp.hasPreciseTarget()) {
        // Call to precise type can go via JTOC
        RVMMethod target = methOp.getTarget();
        Call.setAddress(v,
                        InsertLoadOffsetJTOC(v,
                                             ir,
                                             REF_LOAD,
                                             TypeReference.CodeArray,
                                             target.findOrCreateJtocOffset()));
      } else {
        Operand tib = getTIB(v, ir, Call.getParam(v, 0).copy(), Call.getGuard(v).copy());
        Call.setAddress(v,
                        InsertLoadOffset(v,
                                         ir,
                                         REF_LOAD,
                                         TypeReference.CodeArray,
                                         tib,
                                         Call.getClearAddress(v),
                                         null,
                                         TG()));
      }
    } else if (methOp.isSpecial()) {
      RVMMethod target = methOp.getTarget();
      if (target == null || target.isObjectInitializer() || target.isStatic()) {
        // target == null => we are calling an unresolved <init> method.
        Call.setAddress(v, InsertLoadOffsetJTOC(v, ir, REF_LOAD, TypeReference.CodeArray, Call.getClearAddress(v)));
      } else {
        if (CALL_VIA_JTOC) {
          Call.setAddress(v,
                          InsertLoadOffsetJTOC(v,
                                               ir,
                                               REF_LOAD,
                                               TypeReference.CodeArray,
                                               target.findOrCreateJtocOffset()));
        } else {
          // invoking a virtual method; do it via TIB of target's declaring class.
          Operand tib = getTIB(v, ir, target.getDeclaringClass());
          Call.setAddress(v,
                          InsertLoadOffset(v,
                                           ir,
                                           REF_LOAD,
                                           TypeReference.CodeArray,
                                           tib,
                                           Call.getClearAddress(v),
                                           null,
                                           TG()));
        }
      }
    } else {
      if (VM.VerifyAssertions) VM._assert(methOp.isInterface());
      if (VM.VerifyAssertions) VM._assert(!Call.hasAddress(v));
      if (VM.BuildForIMTInterfaceInvocation) {
        // SEE ALSO: FinalMIRExpansion (for hidden parameter)
        Operand RHStib = getTIB(v, ir, Call.getParam(v, 0).copy(), Call.getGuard(v).copy());
        InterfaceMethodSignature sig = InterfaceMethodSignature.findOrCreate(methOp.getMemberRef());
        Offset offset = sig.getIMTOffset();
        RegisterOperand address = null;
        RegisterOperand IMT =
          InsertLoadOffset(v,
                           ir,
                           REF_LOAD,
                           TypeReference.IMT,
                           RHStib.copy(),
                           Offset.fromIntZeroExtend(TIB_INTERFACE_DISPATCH_TABLE_INDEX << LOG_BYTES_IN_ADDRESS));
        address = InsertLoadOffset(v, ir, REF_LOAD, TypeReference.CodeArray, IMT.copyD2U(), offset);

        Call.setAddress(v, address);
      } else {
        int itableIndex = -1;
        if (VM.BuildForITableInterfaceInvocation && methOp.hasTarget()) {
          RVMClass I = methOp.getTarget().getDeclaringClass();
          // search ITable variant
          itableIndex =
              InterfaceInvocation.getITableIndex(I,
                                                    methOp.getMemberRef().getName(),
                                                    methOp.getMemberRef().getDescriptor());
        }
        if (itableIndex == -1) {
          // itable index is not known at compile-time.
          // call "invokeinterface" to resolve the object and method id
          // into a method address
          RegisterOperand realAddrReg = ir.regpool.makeTemp(TypeReference.CodeArray);
          RVMMethod target = Entrypoints.invokeInterfaceMethod;
          Instruction vp =
              Call.create2(CALL,
                           realAddrReg,
                           AC(target.getOffset()),
                           MethodOperand.STATIC(target),
                           Call.getParam(v, 0).asRegister().copyU2U(),
                           IC(methOp.getMemberRef().getId()));
          vp.position = v.position;
          vp.bcIndex = RUNTIME_SERVICES_BCI;
          v.insertBefore(vp);
          callHelper(vp, ir);
          Call.setAddress(v, realAddrReg.copyD2U());
          return v;
        } else {
          // itable index is known at compile-time.
          // call "findITable" to resolve object + interface id into
          // itable address
          RegisterOperand iTable = ir.regpool.makeTemp(TypeReference.ITable);
          Operand RHStib = getTIB(v, ir, Call.getParam(v, 0).copy(), Call.getGuard(v).copy());
          RVMMethod target = Entrypoints.findItableMethod;
          Instruction fi =
              Call.create2(CALL,
                           iTable,
                           AC(target.getOffset()),
                           MethodOperand.STATIC(target),
                           RHStib,
                           IC(methOp.getTarget().getDeclaringClass().getInterfaceId()));
          fi.position = v.position;
          fi.bcIndex = RUNTIME_SERVICES_BCI;
          v.insertBefore(fi);
          callHelper(fi, ir);
          RegisterOperand address =
              InsertLoadOffset(v,
                               ir,
                               REF_LOAD,
                               TypeReference.CodeArray,
                               iTable.copyD2U(),
                               Offset.fromIntZeroExtend(itableIndex << LOG_BYTES_IN_ADDRESS));
          Call.setAddress(v, address);
          return v;
        }
      }
    }
    return v;
  }

  /**
   * Generate the code to resolve a member (field/method) reference.
   * @param s the RESOLVE_MEMBER instruction to expand
   * @param ir the containing ir object
   * @return the last expanded instruction
   */
  private static Instruction resolveMember(Instruction s, IR ir) {
    Operand memberOp = Unary.getClearVal(s);
    RegisterOperand offset = Unary.getClearResult(s);
    int dictId;
    if (memberOp instanceof LocationOperand) {
      dictId = ((LocationOperand) memberOp).getFieldRef().getId();
    } else {
      dictId = ((MethodOperand) memberOp).getMemberRef().getId();
    }

    BranchProfileOperand bp = BranchProfileOperand.never();
    BasicBlock predBB = s.getBasicBlock();
    BasicBlock succBB = predBB.splitNodeAt(s.getPrev(), ir);
    BasicBlock testBB = predBB.createSubBlock(s.bcIndex, ir, 1f - bp.takenProbability);
    BasicBlock resolveBB = predBB.createSubBlock(s.bcIndex, ir, bp.takenProbability);
    s.remove();

    // Get the offset from the appropriate RVMClassLoader array
    // and check to see if it is valid
    RegisterOperand offsetTable = getStatic(testBB.lastInstruction(), ir, Entrypoints.memberOffsetsField);
    testBB.appendInstruction(Load.create(INT_LOAD,
                                         offset.copyRO(),
                                         offsetTable,
                                         AC(Offset.fromIntZeroExtend(dictId << LOG_BYTES_IN_INT)),
                                         new LocationOperand(TypeReference.Int),
                                         TG()));
    testBB.appendInstruction(Unary.create(INT_2ADDRSigExt, offset, offset.copy()));
    testBB.appendInstruction(IfCmp.create(REF_IFCMP,
        ir.regpool.makeTempValidation(),
        offset.copy(),
        AC(Address.fromIntSignExtend(NEEDS_DYNAMIC_LINK)),
        ConditionOperand.EQUAL(),
        resolveBB.makeJumpTarget(),
        bp));

    // Handle the offset being invalid
    resolveBB.appendInstruction(CacheOp.mutate(s, RESOLVE, memberOp));
    resolveBB.appendInstruction(testBB.makeGOTO());

    // Put together the CFG links & code order
    predBB.insertOut(testBB);
    ir.cfg.linkInCodeOrder(predBB, testBB);
    testBB.insertOut(succBB);
    testBB.insertOut(resolveBB);
    ir.cfg.linkInCodeOrder(testBB, succBB);
    resolveBB.insertOut(testBB);              // backedge
    ir.cfg.addLastInCodeOrder(resolveBB);  // stick resolution code in outer space.
    return testBB.lastInstruction();
  }

  /**
   * Insert a binary instruction before s in the instruction stream.
   * @param s the instruction to insert before
   * @param ir the containing IR
   * @param operator the operator to insert
   * @param type the type of the result
   * @param o1 the first operand
   * @param o2 the second operand
   * @return the result operand of the inserted instruction
   */
  public static RegisterOperand insertBinary(Instruction s, IR ir, Operator operator,
                                                 TypeReference type, Operand o1, Operand o2) {
    RegisterOperand t = ir.regpool.makeTemp(type);
    s.insertBefore(Binary.create(operator, t, o1, o2));
    return t.copyD2U();
  }

  /**
   * Insert a unary instruction before s in the instruction stream.
   * @param s the instruction to insert before
   * @param ir the containing IR
   * @param operator the operator to insert
   * @param type the type of the result
   * @param o1 the operand
   * @return the result operand of the inserted instruction
   */
  static RegisterOperand InsertUnary(Instruction s, IR ir, Operator operator, TypeReference type,
                                         Operand o1) {
    RegisterOperand t = ir.regpool.makeTemp(type);
    s.insertBefore(Unary.create(operator, t, o1));
    return t.copyD2U();
  }

  /**
   * Insert a guarded unary instruction before s in the instruction stream.
   * @param s the instruction to insert before
   * @param ir the containing IR
   * @param operator the operator to insert
   * @param type the type of the result
   * @param o1 the operand
   * @param guard the guard operand
   * @return the result operand of the inserted instruction
   */
  static RegisterOperand InsertGuardedUnary(Instruction s, IR ir, Operator operator,
                                                TypeReference type, Operand o1, Operand guard) {
    RegisterOperand t = ir.regpool.makeTemp(type);
    s.insertBefore(GuardedUnary.create(operator, t, o1, guard));
    return t.copyD2U();
  }

  /**
   * Insert a load off the JTOC before s in the instruction stream.
   * @param s the instruction to insert before
   * @param ir the containing IR
   * @param operator the operator to insert
   * @param type the type of the result
   * @param offset the offset to load at
   * @return the result operand of the inserted instruction
   */
  static RegisterOperand InsertLoadOffsetJTOC(Instruction s, IR ir, Operator operator,
                                                  TypeReference type, Offset offset) {
    return InsertLoadOffset(s,
                            ir,
                            operator,
                            type,
                            ir.regpool.makeJTOCOp(ir, s),
                            AC(offset),
                            new LocationOperand(offset),
                            null);
  }

  /**
   * Insert a load off the JTOC before s in the instruction stream.
   * @param s the instruction to insert before
   * @param ir the containing IR
   * @param operator the operator to insert
   * @param type the type of the result
   * @param offset the offset to load at
   * @return the result operand of the inserted instruction
   */
  static RegisterOperand InsertLoadOffsetJTOC(Instruction s, IR ir, Operator operator,
                                                  TypeReference type, Operand offset) {
    return InsertLoadOffset(s, ir, operator, type, ir.regpool.makeJTOCOp(ir, s), offset, null, null);
  }

  /**
   * Insert a load off before s in the instruction stream.
   * @param s the instruction to insert before
   * @param ir the containing IR
   * @param operator the operator to insert
   * @param type the type of the result
   * @param reg2 the base to load from
   * @param offset the offset to load at
   * @return the result operand of the inserted instruction
   */
  static RegisterOperand InsertLoadOffset(Instruction s, IR ir, Operator operator,
                                              TypeReference type, Operand reg2, Offset offset) {
    return InsertLoadOffset(s, ir, operator, type, reg2, offset, null, null);
  }

  /**
   * Insert a load off before s in the instruction stream.
   * @param s the instruction to insert before
   * @param ir the containing IR
   * @param operator the operator to insert
   * @param type the type of the result
   * @param reg2 the base to load from
   * @param offset the offset to load at
   * @param guard the guard operand
   * @return the result operand of the inserted instruction
   */
  static RegisterOperand InsertLoadOffset(Instruction s, IR ir, Operator operator,
                                              TypeReference type, Operand reg2, Offset offset,
                                              Operand guard) {
    return InsertLoadOffset(s, ir, operator, type, reg2, offset, null, guard);
  }

  /**
   * Insert a load off before s in the instruction stream.
   * @param s the instruction to insert before
   * @param ir the containing IR
   * @param operator the operator to insert
   * @param type the type of the result
   * @param reg2 the base to load from
   * @param offset the offset to load at
   * @param loc the location operand
   * @param guard the guard operand
   * @return the result operand of the inserted instruction
   */
  static RegisterOperand InsertLoadOffset(Instruction s, IR ir, Operator operator,
                                              TypeReference type, Operand reg2, Offset offset,
                                              LocationOperand loc, Operand guard) {
    return InsertLoadOffset(s, ir, operator, type, reg2, AC(offset), loc, guard);
  }

  /**
   * Insert a load off before s in the instruction stream.
   * @param s the instruction to insert before
   * @param ir the containing IR
   * @param operator the operator to insert
   * @param type the type of the result
   * @param reg2 the base to load from
   * @param offset the offset to load at
   * @param loc the location operand
   * @param guard the guard operand
   * @return the result operand of the inserted instruction
   */
  static RegisterOperand InsertLoadOffset(Instruction s, IR ir, Operator operator,
                                              TypeReference type, Operand reg2, Operand offset,
                                              LocationOperand loc, Operand guard) {
    RegisterOperand regTarget = ir.regpool.makeTemp(type);
    Instruction s2 = Load.create(operator, regTarget, reg2, offset, loc, guard);
    s.insertBefore(s2);
    return regTarget.copyD2U();
  }

  /** get the tib from the object pointer to by obj */
  static Operand getTIB(Instruction s, IR ir, Operand obj, Operand guard) {
    if (obj.isObjectConstant()) {
      // NB Constant types must already be resolved
      try {
        RVMType type = obj.getType().resolve();
        return new TIBConstantOperand(type);
      } catch (NoClassDefFoundError e) {
        if (VM.runningVM) throw e;
        // Class not found during bootstrap due to chasing a class
        // only valid in the bootstrap JVM
      }
    }
    RegisterOperand res = ir.regpool.makeTemp(TypeReference.TIB);
    Instruction s2 = GuardedUnary.create(GET_OBJ_TIB, res, obj, guard);
    s.insertBefore(s2);
    return res.copyD2U();
  }

  /** get the class tib for type */
  static Operand getTIB(Instruction s, IR ir, RVMType type) {
    return new TIBConstantOperand(type);
    //return getTIB(s, ir, new TypeOperand(type));
  }

  /** get the class tib for type */
  static Operand getTIB(Instruction s, IR ir, TypeOperand type) {
    RVMType t = type.getVMType();
    if (VM.BuildForIA32 && !MemoryManagerConstants.MOVES_TIBS && VM.runningVM && t != null && t.isResolved()) {
      Address addr = Magic.objectAsAddress(t.getTypeInformationBlock());
      return new AddressConstantOperand(addr);
    } else if (!t.isResolved()) {
      RegisterOperand res = ir.regpool.makeTemp(TypeReference.TIB);
      s.insertBefore(Unary.create(GET_CLASS_TIB, res, type));
      return res.copyD2U();
    } else {
      return new TIBConstantOperand(t);
    }
  }

  /**
   * Get an instance method from a TIB
   */
  static RegisterOperand getInstanceMethod(Instruction s, IR ir, Operand tib, RVMMethod method) {
    return InsertLoadOffset(s, ir, REF_LOAD, TypeReference.CodeArray, tib, method.getOffset());
  }

  /**
   * Load an instance field.
   * @param s
   * @param ir
   * @param obj
   * @param field
   */
  public static RegisterOperand getField(Instruction s, IR ir, RegisterOperand obj, RVMField field) {
    return getField(s, ir, obj, field, null);
  }

  /**
   * Load an instance field.
   * @param s
   * @param ir
   * @param obj
   * @param field
   * @param guard
   */
  static RegisterOperand getField(Instruction s, IR ir, RegisterOperand obj, RVMField field,
                                      Operand guard) {
    return InsertLoadOffset(s,
                            ir,
                            IRTools.getLoadOp(field.getType(), field.isStatic()),
                            field.getType(),
                            obj,
                            field.getOffset(),
                            new LocationOperand(field),
                            guard);
  }

  /*  RRB 100500 */
  /**
   * support for direct call to specialized method.
   */
  static RegisterOperand getSpecialMethod(Instruction s, IR ir, int smid) {
    //  First, get the pointer to the JTOC offset pointing to the
    //  specialized Method table
    RegisterOperand reg =
        InsertLoadOffsetJTOC(s,
                             ir,
                             REF_LOAD,
                             TypeReference.JavaLangObjectArray,
                             AosEntrypoints.specializedMethodsField.getOffset());
    RegisterOperand instr =
        InsertLoadOffset(s,
                         ir,
                         REF_LOAD,
                         TypeReference.CodeArray,
                         reg,
                         Offset.fromIntZeroExtend(smid << LOG_BYTES_IN_INT));
    return instr;
  }

  /**
   * Expand symbolic SysCall target into a chain of loads from the bootrecord to
   * the desired target address.
   */
  public static void expandSysCallTarget(Instruction s, IR ir) {
    MethodOperand sysM = Call.getMethod(s);
    if (sysM.getMemberRef().isFieldReference()) {
      RegisterOperand t1 = getStatic(s, ir, Entrypoints.the_boot_recordField);
      RVMField target = sysM.getMemberRef().asFieldReference().resolve();
      Operand ip = getField(s, ir, t1, target);
      Call.setAddress(s, ip);
    }
  }

  /**
   * Load a static field.
   * @param s
   * @param ir
   * @param field
   */
  public static RegisterOperand getStatic(Instruction s, IR ir, RVMField field) {
    return InsertLoadOffsetJTOC(s,
                                ir,
                                IRTools.getLoadOp(field.getType(), field.isStatic()),
                                field.getType(),
                                field.getOffset());
  }
}
