/*
 *  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.mir2mc.ia32;

import org.jikesrvm.VM;
import org.jikesrvm.classloader.RVMMethod;
import org.jikesrvm.classloader.TypeReference;
import org.jikesrvm.compilers.opt.ir.BBend;
import org.jikesrvm.compilers.opt.ir.Label;
import org.jikesrvm.compilers.opt.ir.MIR_BinaryAcc;
import org.jikesrvm.compilers.opt.ir.MIR_Branch;
import org.jikesrvm.compilers.opt.ir.MIR_Call;
import org.jikesrvm.compilers.opt.ir.MIR_CaseLabel;
import org.jikesrvm.compilers.opt.ir.MIR_Compare;
import org.jikesrvm.compilers.opt.ir.MIR_CondBranch;
import org.jikesrvm.compilers.opt.ir.MIR_CondBranch2;
import org.jikesrvm.compilers.opt.ir.MIR_Empty;
import org.jikesrvm.compilers.opt.ir.MIR_Lea;
import org.jikesrvm.compilers.opt.ir.MIR_LowTableSwitch;
import org.jikesrvm.compilers.opt.ir.MIR_Move;
import org.jikesrvm.compilers.opt.ir.MIR_Nullary;
import org.jikesrvm.compilers.opt.ir.MIR_Return;
import org.jikesrvm.compilers.opt.ir.MIR_Set;
import org.jikesrvm.compilers.opt.ir.MIR_Test;
import org.jikesrvm.compilers.opt.ir.MIR_Trap;
import org.jikesrvm.compilers.opt.ir.MIR_TrapIf;
import org.jikesrvm.compilers.opt.ir.MIR_Unary;
import org.jikesrvm.compilers.opt.ir.MIR_UnaryNoRes;
import org.jikesrvm.compilers.opt.ir.MIR_XChng;
import org.jikesrvm.compilers.opt.ir.NullCheck;
import org.jikesrvm.compilers.opt.ir.BasicBlock;
import org.jikesrvm.compilers.opt.ir.IR;
import org.jikesrvm.compilers.opt.ir.IRTools;
import org.jikesrvm.compilers.opt.ir.Instruction;
import org.jikesrvm.compilers.opt.ir.OperandEnumeration;

import static org.jikesrvm.compilers.opt.ir.Operators.ADVISE_ESP_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.CALL_SAVE_VOLATILE;
import static org.jikesrvm.compilers.opt.ir.Operators.CALL_SAVE_VOLATILE_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.DUMMY_DEF_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.DUMMY_USE_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.IA32_ADD;
import static org.jikesrvm.compilers.opt.ir.Operators.IA32_CALL;
import static org.jikesrvm.compilers.opt.ir.Operators.IA32_CMP;
import static org.jikesrvm.compilers.opt.ir.Operators.IA32_CMPXCHG;
import static org.jikesrvm.compilers.opt.ir.Operators.IA32_CMPXCHG8B;
import static org.jikesrvm.compilers.opt.ir.Operators.IA32_FCLEAR_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.IA32_FFREE;
import static org.jikesrvm.compilers.opt.ir.Operators.IA32_FLD;
import static org.jikesrvm.compilers.opt.ir.Operators.IA32_FMOV_ENDING_LIVE_RANGE_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.IA32_FMOV_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.IA32_FST;
import static org.jikesrvm.compilers.opt.ir.Operators.IA32_FSTP;
import static org.jikesrvm.compilers.opt.ir.Operators.IA32_FXCH;
import static org.jikesrvm.compilers.opt.ir.Operators.IA32_INT;
import static org.jikesrvm.compilers.opt.ir.Operators.IA32_JCC;
import static org.jikesrvm.compilers.opt.ir.Operators.IA32_JCC2_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.IA32_JMP;
import static org.jikesrvm.compilers.opt.ir.Operators.IA32_LEA_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.IA32_LOCK;
import static org.jikesrvm.compilers.opt.ir.Operators.IA32_LOCK_CMPXCHG8B_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.IA32_LOCK_CMPXCHG_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.IA32_MOV;
import static org.jikesrvm.compilers.opt.ir.Operators.IA32_MOV_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.IA32_MOVZX__B;
import static org.jikesrvm.compilers.opt.ir.Operators.IA32_OFFSET;
import static org.jikesrvm.compilers.opt.ir.Operators.IA32_RET;
import static org.jikesrvm.compilers.opt.ir.Operators.IA32_SET__B_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.IA32_SHL;
import static org.jikesrvm.compilers.opt.ir.Operators.IA32_TEST_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.IA32_TRAPIF;
import static org.jikesrvm.compilers.opt.ir.Operators.IA32_TRAPIF_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.IA32_XOR;
import static org.jikesrvm.compilers.opt.ir.Operators.MIR_LOWTABLESWITCH_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.NULL_CHECK_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.REQUIRE_ESP_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.YIELDPOINT_BACKEDGE_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.YIELDPOINT_EPILOGUE_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.YIELDPOINT_OSR_opcode;
import static org.jikesrvm.compilers.opt.ir.Operators.YIELDPOINT_PROLOGUE_opcode;
import org.jikesrvm.compilers.opt.ir.Register;
import org.jikesrvm.compilers.opt.ir.ia32.PhysicalDefUse;
import org.jikesrvm.compilers.opt.ir.ia32.PhysicalRegisterSet;
import org.jikesrvm.compilers.opt.ir.operand.BranchProfileOperand;
import org.jikesrvm.compilers.opt.ir.operand.IntConstantOperand;
import org.jikesrvm.compilers.opt.ir.operand.LocationOperand;
import org.jikesrvm.compilers.opt.ir.operand.MemoryOperand;
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.TrapCodeOperand;
import org.jikesrvm.compilers.opt.ir.operand.ia32.IA32ConditionOperand;
import org.jikesrvm.runtime.ArchEntrypoints;
import org.jikesrvm.runtime.Entrypoints;
import org.jikesrvm.runtime.Magic;
import org.vmmagic.unboxed.Offset;

/**
 * Final acts of MIR expansion for the IA32 architecture.
 * Things that are expanded here (immediately before final assembly)
 * should only be those sequences that cannot be expanded earlier
 * due to difficulty in keeping optimizations from interfering with them.
 *
 * One job of this phase is to handle the expansion of the remains of
 * table switch.  The code looks like a mess (which it is), but there
 * is little choice for relocatable IA32 code that does this.  And the
 * details of this code are shared with the baseline compiler and
 * dependent in detail on the Assembler (see {@link
 * org.jikesrvm.compilers.common.assembler.ia32.Assembler#emitOFFSET_Imm_ImmOrLabel}).  If you want to mess with
 * it, you will probably need to mess with them as well.
 */
public class FinalMIRExpansion extends IRTools {

  /**
   * @param ir the IR to expand
   * @return return value is garbage for IA32
   */
  public static int expand(IR ir) {
    PhysicalRegisterSet phys = ir.regpool.getPhysicalRegisterSet();

    for (Instruction next, p = ir.firstInstructionInCodeOrder(); p != null; p = next) {
      next = p.nextInstructionInCodeOrder();
      p.setmcOffset(-1);
      p.scratchObject = null;

      switch (p.getOpcode()) {
        case MIR_LOWTABLESWITCH_opcode: {
          // split the basic block after the MIR_LOWTABLESWITCH
          BasicBlock thisBlock = p.getBasicBlock();
          BasicBlock nextBlock = thisBlock.splitNodeWithLinksAt(p, ir);
          nextBlock.firstInstruction().setmcOffset(-1);

          // place offset data table after call so that call pushes
          // the base address of this table onto the stack
          int NumTargets = MIR_LowTableSwitch.getNumberOfTargets(p);
          for (int i = 0; i < NumTargets; i++) {
            thisBlock.appendInstruction(MIR_CaseLabel.create(IA32_OFFSET,
                                                             IC(i),
                                                             MIR_LowTableSwitch.getClearTarget(p, i)));
          }
          // calculate address to which to jump, and store it
          // on the top of the stack
          Register regS = MIR_LowTableSwitch.getIndex(p).getRegister();
          nextBlock.appendInstruction(MIR_BinaryAcc.create(IA32_SHL,
                                                           new RegisterOperand(regS, TypeReference.Int),
                                                           IC(2)));
          nextBlock.appendInstruction(MIR_BinaryAcc.create(IA32_ADD,
                                                           new RegisterOperand(regS, TypeReference.Int),
                                                           MemoryOperand.I(new RegisterOperand(phys.getESP(),
                                                                                                       TypeReference.Int),
                                                                               (byte) 4,
                                                                               null,
                                                                               null)));
          nextBlock.appendInstruction(MIR_Move.create(IA32_MOV,
                                                      new RegisterOperand(regS, TypeReference.Int),
                                                      MemoryOperand.I(new RegisterOperand(regS,
                                                                                                  TypeReference.Int),
                                                                          (byte) 4,
                                                                          null,
                                                                          null)));
          nextBlock.appendInstruction(MIR_BinaryAcc.create(IA32_ADD,
                                                           MemoryOperand.I(new RegisterOperand(phys.getESP(),
                                                                                                       TypeReference.Int),
                                                                               (byte) 4,
                                                                               null,
                                                                               null),
                                                           new RegisterOperand(regS, TypeReference.Int)));
          // ``return'' to mangled return address
          nextBlock.appendInstruction(MIR_Return.create(IA32_RET, IC(0), null, null));

          // CALL next block to push pc of next ``instruction'' onto stack
          MIR_Call.mutate0(p, IA32_CALL, null, null, nextBlock.makeJumpTarget(), null);
        }
        break;

        case IA32_TEST_opcode:
          // don't bother telling rest of compiler that memory operand
          // must be first; we can just commute it here.
          if (MIR_Test.getVal2(p).isMemory()) {
            Operand tmp = MIR_Test.getClearVal1(p);
            MIR_Test.setVal1(p, MIR_Test.getClearVal2(p));
            MIR_Test.setVal2(p, tmp);
          }
          break;

        case NULL_CHECK_opcode: {
          // mutate this into a TRAPIF, and then fall through to the the
          // TRAP_IF case.
          Operand ref = NullCheck.getRef(p);
          MIR_TrapIf.mutate(p,
                            IA32_TRAPIF,
                            null,
                            ref.copy(),
                            IC(0),
                            IA32ConditionOperand.EQ(),
                            TrapCodeOperand.NullPtr());
        }
        // There is no break statement here on purpose!
        case IA32_TRAPIF_opcode: {
          // split the basic block right before the IA32_TRAPIF
          BasicBlock thisBlock = p.getBasicBlock();
          BasicBlock trap = thisBlock.createSubBlock(p.bcIndex, ir, 0f);
          thisBlock.insertOut(trap);
          BasicBlock nextBlock = thisBlock.splitNodeWithLinksAt(p, ir);
          thisBlock.insertOut(trap);
          TrapCodeOperand tc = MIR_TrapIf.getClearTrapCode(p);
          p.remove();
          nextBlock.firstInstruction().setmcOffset(-1);
          // add code to thisBlock to conditionally jump to trap
          Instruction cmp = MIR_Compare.create(IA32_CMP, MIR_TrapIf.getVal1(p), MIR_TrapIf.getVal2(p));
          if (p.isMarkedAsPEI()) {
            // The trap if was explictly marked, which means that it has
            // a memory operand into which we've folded a null check.
            // Actually need a GC map for both the compare and the INT.
            cmp.markAsPEI();
            cmp.copyPosition(p);
            ir.MIRInfo.gcIRMap.insertTwin(p, cmp);
          }
          thisBlock.appendInstruction(cmp);
          thisBlock.appendInstruction(MIR_CondBranch.create(IA32_JCC,
                                                            MIR_TrapIf.getCond(p),
                                                            trap.makeJumpTarget(),
                                                            null));

          // add block at end to hold trap instruction, and
          // insert trap sequence
          ir.cfg.addLastInCodeOrder(trap);
          if (tc.isArrayBounds()) {
            // attempt to store index expression in processor object for
            // C trap handler
            Operand index = MIR_TrapIf.getVal2(p);
            if (!(index instanceof RegisterOperand || index instanceof IntConstantOperand)) {
              index = IC(0xdeadbeef); // index was spilled, and
              // we can't get it back here.
            }
            MemoryOperand mo =
                MemoryOperand.BD(ir.regpool.makePROp(),
                                     ArchEntrypoints.arrayIndexTrapParamField.getOffset(),
                                     (byte) 4,
                                     null,
                                     null);
            trap.appendInstruction(MIR_Move.create(IA32_MOV, mo, index.copy()));
          }
          // NOTE: must make p the trap instruction: it is the GC point!
          // IMPORTANT: must also inform the GCMap that the instruction has
          // been moved!!!
          trap.appendInstruction(MIR_Trap.mutate(p, IA32_INT, null, tc));
          ir.MIRInfo.gcIRMap.moveToEnd(p);

          if (tc.isStackOverflow()) {
            // only stackoverflow traps resume at next instruction.
            trap.appendInstruction(MIR_Branch.create(IA32_JMP, nextBlock.makeJumpTarget()));
          }
        }
        break;

        case IA32_FMOV_ENDING_LIVE_RANGE_opcode: {
          Operand result = MIR_Move.getResult(p);
          Operand value = MIR_Move.getValue(p);
          if (result.isRegister() && value.isRegister()) {
            if (result.similar(value)) {
              // eliminate useless move
              p.remove();
            } else {
              int i = PhysicalRegisterSet.getFPRIndex(result.asRegister().getRegister());
              int j = PhysicalRegisterSet.getFPRIndex(value.asRegister().getRegister());
              if (i == 0) {
                MIR_XChng.mutate(p, IA32_FXCH, result, value);
              } else if (j == 0) {
                MIR_XChng.mutate(p, IA32_FXCH, value, result);
              } else {
                expandFmov(p, phys);
              }
            }
          } else {
            expandFmov(p, phys);
          }
          break;
        }

        case DUMMY_DEF_opcode:
        case DUMMY_USE_opcode:
        case REQUIRE_ESP_opcode:
        case ADVISE_ESP_opcode:
          p.remove();
          break;

        case IA32_FMOV_opcode:
          expandFmov(p, phys);
          break;

        case IA32_MOV_opcode:
          // Replace result = IA32_MOV 0 with result = IA32_XOR result, result
          if (MIR_Move.getResult(p).isRegister() &&
              MIR_Move.getValue(p).isIntConstant() &&
              MIR_Move.getValue(p).asIntConstant().value == 0) {
            // Calculate what flags are defined in coming instructions before a use of a flag or BBend
            Instruction x = next;
            int futureDefs = 0;
            while(!BBend.conforms(x) && !PhysicalDefUse.usesEFLAGS(x.operator)) {
              futureDefs |= x.operator.implicitDefs;
              x = x.nextInstructionInCodeOrder();
            }
            // If the flags will be destroyed prior to use or we reached the end of the basic block
            if (BBend.conforms(x) ||
                (futureDefs & PhysicalDefUse.maskAF_CF_OF_PF_SF_ZF) == PhysicalDefUse.maskAF_CF_OF_PF_SF_ZF) {
              Operand result = MIR_Move.getClearResult(p);
              MIR_BinaryAcc.mutate(p, IA32_XOR, result, result.copy());
            }
          }
          break;

        case IA32_SET__B_opcode:
          // Replace <cmp>, set__b, movzx__b with xor, <cmp>, set__b
          if (MIR_Set.getResult(p).isRegister() &&
              MIR_Unary.conforms(next) &&
              (next.operator() == IA32_MOVZX__B) &&
              MIR_Unary.getResult(next).isRegister() &&
              MIR_Unary.getVal(next).similar(MIR_Unary.getResult(next)) &&
              MIR_Unary.getVal(next).similar(MIR_Set.getResult(p))) {
            // Find instruction in this basic block that defines flags
            Instruction x = p.getPrev();
            Operand result = MIR_Unary.getResult(next);
            boolean foundCmp = false;
            outer:
            while(!Label.conforms(x)) {
              OperandEnumeration e = x.getUses();
              while(e.hasMoreElements()) {
                // We can't use an xor to clear the register if that register is
                // used by the <cmp> or intervening instruction
                if (e.next().similar(result)) {
                  break outer;
                }
              }
              if (PhysicalDefUse.definesEFLAGS(x.operator) &&
                  !PhysicalDefUse.usesEFLAGS(x.operator)) {
                // we found a <cmp> that doesn't use the result or the flags
                // that would be clobbered by the xor
                foundCmp = true;
                break outer;
              }
              x = x.getPrev();
            }
            if (foundCmp) {
              // We found the <cmp>, mutate the movzx__b into an xor and insert it before the <cmp>
              next.remove();
              MIR_BinaryAcc.mutate(next, IA32_XOR, result, MIR_Unary.getVal(next));
              x.insertBefore(next);
              // get ready for the next instruction
              next = p.nextInstructionInCodeOrder();
            }
          }
          break;

        case IA32_LEA_opcode: {
          // Sometimes we're over eager in BURS in using LEAs and after register
          // allocation we can simplify to the accumulate form
          // replace reg1 = LEA [reg1 + reg2] with reg1 = reg1 + reg2
          // replace reg1 = LEA [reg1 + c1] with reg1 = reg1 + c1
          // replace reg1 = LEA [reg1 << c1] with reg1 = reg1 << c1
          MemoryOperand value = MIR_Lea.getValue(p);
          RegisterOperand result = MIR_Lea.getResult(p);
          if ((value.base != null && value.base.getRegister() == result.getRegister()) ||
              (value.index != null && value.index.getRegister() == result.getRegister())) {
            // Calculate what flags are defined in coming instructions before a use of a flag or BBend
            Instruction x = next;
            int futureDefs = 0;
            while(!BBend.conforms(x) && !PhysicalDefUse.usesEFLAGS(x.operator)) {
              futureDefs |= x.operator.implicitDefs;
              x = x.nextInstructionInCodeOrder();
            }
            // If the flags will be destroyed prior to use or we reached the end of the basic block
            if (BBend.conforms(x) ||
                (futureDefs & PhysicalDefUse.maskAF_CF_OF_PF_SF_ZF) == PhysicalDefUse.maskAF_CF_OF_PF_SF_ZF) {
              if (value.base != null &&
                  value.index != null && value.index.getRegister() == result.getRegister() &&
                  value.disp.isZero() &&
                  value.scale == 0) {
                // reg1 = lea [base + reg1] -> add reg1, base
                MIR_BinaryAcc.mutate(p, IA32_ADD, result, value.base);
              } else if (value.base != null && value.base.getRegister() == result.getRegister() &&
                         value.index != null &&
                         value.disp.isZero() &&
                         value.scale == 0) {
                // reg1 = lea [reg1 + index] -> add reg1, index
                MIR_BinaryAcc.mutate(p, IA32_ADD, result, value.index);
              } else if (value.base != null && value.base.getRegister() == result.getRegister() &&
                         value.index == null) {
                // reg1 = lea [reg1 + disp] -> add reg1, disp
                MIR_BinaryAcc.mutate(p, IA32_ADD, result, IC(value.disp.toInt()));
              } else if (value.base == null &&
                         value.index != null && value.index.getRegister() == result.getRegister() &&
                         value.scale == 0) {
                // reg1 = lea [reg1 + disp] -> add reg1, disp
                MIR_BinaryAcc.mutate(p, IA32_ADD, result, IC(value.disp.toInt()));
              } else if (value.base == null &&
                         value.index != null && value.index.getRegister() == result.getRegister() &&
                         value.disp.isZero()) {
                // reg1 = lea [reg1 << scale] -> shl reg1, scale
                MIR_BinaryAcc.mutate(p, IA32_SHL, result, IC(value.scale));
              }
            }
          }
        }
        break;

        case IA32_FCLEAR_opcode:
          expandFClear(p, ir);
          break;

        case IA32_JCC2_opcode:
          p.insertBefore(MIR_CondBranch.create(IA32_JCC,
                                               MIR_CondBranch2.getCond1(p),
                                               MIR_CondBranch2.getTarget1(p),
                                               MIR_CondBranch2.getBranchProfile1(p)));
          MIR_CondBranch.mutate(p,
                                IA32_JCC,
                                MIR_CondBranch2.getCond2(p),
                                MIR_CondBranch2.getTarget2(p),
                                MIR_CondBranch2.getBranchProfile2(p));
          break;

        case CALL_SAVE_VOLATILE_opcode:
          p.operator = IA32_CALL;
          break;

        case IA32_LOCK_CMPXCHG_opcode:
          p.insertBefore(MIR_Empty.create(IA32_LOCK));
          p.operator = IA32_CMPXCHG;
          break;

        case IA32_LOCK_CMPXCHG8B_opcode:
          p.insertBefore(MIR_Empty.create(IA32_LOCK));
          p.operator = IA32_CMPXCHG8B;
          break;

        case YIELDPOINT_PROLOGUE_opcode:
          expandYieldpoint(p, ir, Entrypoints.optThreadSwitchFromPrologueMethod, IA32ConditionOperand.NE());
          break;

        case YIELDPOINT_EPILOGUE_opcode:
          expandYieldpoint(p, ir, Entrypoints.optThreadSwitchFromEpilogueMethod, IA32ConditionOperand.NE());
          break;

        case YIELDPOINT_BACKEDGE_opcode:
          expandYieldpoint(p, ir, Entrypoints.optThreadSwitchFromBackedgeMethod, IA32ConditionOperand.GT());
          break;

        case YIELDPOINT_OSR_opcode:
          // must yield, does not check threadSwitch request
          expandUnconditionalYieldpoint(p, ir, Entrypoints.optThreadSwitchFromOsrOptMethod);
          break;

      }
    }
    return 0;
  }

  /**
   * expand an FCLEAR pseudo-insruction using FFREEs.
   *
   * @param s the instruction to expand
   * @param ir the containing IR
   */
  private static void expandFClear(Instruction s, IR ir) {
    int nSave = MIR_UnaryNoRes.getVal(s).asIntConstant().value;
    int fpStackHeight = ir.MIRInfo.fpStackHeight;
    PhysicalRegisterSet phys = ir.regpool.getPhysicalRegisterSet();

    for (int i = nSave; i < fpStackHeight; i++) {
      Register f = phys.getFPR(i);
      s.insertBefore(MIR_Nullary.create(IA32_FFREE, D(f)));
    }

    // Remove the FCLEAR.
    s.remove();
  }

  /**
   * expand an FMOV pseudo-insruction.
   *
   * @param s the instruction to expand
   * @param phys controlling physical register set
   */
  private static void expandFmov(Instruction s, PhysicalRegisterSet phys) {
    Operand result = MIR_Move.getResult(s);
    Operand value = MIR_Move.getValue(s);

    if (result.isRegister() && value.isRegister()) {
      if (result.similar(value)) {
        // eliminate useless move
        s.remove();
      } else {
        int i = PhysicalRegisterSet.getFPRIndex(result.asRegister().getRegister());
        int j = PhysicalRegisterSet.getFPRIndex(value.asRegister().getRegister());
        if (j == 0) {
          // We have FMOV Fi, F0
          // Expand as:
          //        FST F(i)  (copy F0 to F(i))
          MIR_Move.mutate(s, IA32_FST, D(phys.getFPR(i)), D(phys.getFPR(0)));
        } else {
          // We have FMOV Fi, Fj
          // Expand as:
          //        FLD Fj  (push Fj on FP stack).
          //        FSTP F(i+1)  (copy F0 to F(i+1) and then pop register stack)
          s.insertBefore(MIR_Move.create(IA32_FLD, D(phys.getFPR(0)), value));

          MIR_Move.mutate(s, IA32_FSTP, D(phys.getFPR(i + 1)), D(phys.getFPR(0)));
        }

      }
    } else if (value instanceof MemoryOperand) {
      if (result instanceof MemoryOperand) {
        // We have FMOV M1, M2
        // Expand as:
        //        FLD M1   (push M1 on FP stack).
        //        FSTP M2  (copy F0 to M2 and pop register stack)
        s.insertBefore(MIR_Move.create(IA32_FLD, D(phys.getFPR(0)), value));
        MIR_Move.mutate(s, IA32_FSTP, result, D(phys.getFPR(0)));
      } else {
        // We have FMOV Fi, M
        // Expand as:
        //        FLD M    (push M on FP stack).
        //        FSTP F(i+1)  (copy F0 to F(i+1) and pop register stack)
        if (VM.VerifyAssertions) VM._assert(result.isRegister());
        int i = PhysicalRegisterSet.getFPRIndex(result.asRegister().getRegister());
        s.insertBefore(MIR_Move.create(IA32_FLD, D(phys.getFPR(0)), value));
        MIR_Move.mutate(s, IA32_FSTP, D(phys.getFPR(i + 1)), D(phys.getFPR(0)));
      }
    } else {
      // We have FMOV M, Fi
      if (VM.VerifyAssertions) VM._assert(value.isRegister());
      if (VM.VerifyAssertions) {
        VM._assert(result instanceof MemoryOperand);
      }
      int i = PhysicalRegisterSet.getFPRIndex(value.asRegister().getRegister());
      if (i != 0) {
        // Expand as:
        //        FLD Fi    (push Fi on FP stack).
        //        FSTP M    (store F0 in M and pop register stack);
        s.insertBefore(MIR_Move.create(IA32_FLD, D(phys.getFPR(0)), value));
        MIR_Move.mutate(s, IA32_FSTP, result, D(phys.getFPR(0)));
      } else {
        // Expand as:
        //        FST M    (store F0 in M);
        MIR_Move.mutate(s, IA32_FST, result, value);
      }
    }
  }

  private static void expandYieldpoint(Instruction s, IR ir, RVMMethod meth, IA32ConditionOperand ypCond) {
    // split the basic block after the yieldpoint, create a new
    // block at the end of the IR to hold the yieldpoint,
    // remove the yieldpoint (to prepare to out it in the new block at the end)
    BasicBlock thisBlock = s.getBasicBlock();
    BasicBlock nextBlock = thisBlock.splitNodeWithLinksAt(s, ir);
    BasicBlock yieldpoint = thisBlock.createSubBlock(s.bcIndex, ir, 0);
    thisBlock.insertOut(yieldpoint);
    yieldpoint.insertOut(nextBlock);
    ir.cfg.addLastInCodeOrder(yieldpoint);
    s.remove();

    // change thread switch instruction into call to thread switch routine
    // NOTE: must make s the call instruction: it is the GC point!
    //       must also inform the GCMap that s has been moved!!!
    Offset offset = meth.getOffset();
    LocationOperand loc = new LocationOperand(offset);
    Operand guard = TG();
    Operand target = MemoryOperand.D(Magic.getTocPointer().plus(offset), (byte) 4, loc, guard);
    MIR_Call.mutate0(s, CALL_SAVE_VOLATILE, null, null, target, MethodOperand.STATIC(meth));
    yieldpoint.appendInstruction(s);
    ir.MIRInfo.gcIRMap.moveToEnd(s);

    yieldpoint.appendInstruction(MIR_Branch.create(IA32_JMP, nextBlock.makeJumpTarget()));

    // Check to see if threadSwitch requested
    Offset tsr = Entrypoints.takeYieldpointField.getOffset();
    MemoryOperand M =
        MemoryOperand.BD(ir.regpool.makePROp(), tsr, (byte) 4, null, null);
    thisBlock.appendInstruction(MIR_Compare.create(IA32_CMP, M, IC(0)));
    thisBlock.appendInstruction(MIR_CondBranch.create(IA32_JCC,
                                                      ypCond,
                                                      yieldpoint.makeJumpTarget(),
                                                      BranchProfileOperand.never()));
  }

  /* generate yieldpoint without checking threadSwith request
   */
  private static void expandUnconditionalYieldpoint(Instruction s, IR ir, RVMMethod meth) {
    // split the basic block after the yieldpoint, create a new
    // block at the end of the IR to hold the yieldpoint,
    // remove the yieldpoint (to prepare to out it in the new block at the end)
    BasicBlock thisBlock = s.getBasicBlock();
    BasicBlock nextBlock = thisBlock.splitNodeWithLinksAt(s, ir);
    BasicBlock yieldpoint = thisBlock.createSubBlock(s.bcIndex, ir);
    thisBlock.insertOut(yieldpoint);
    yieldpoint.insertOut(nextBlock);
    ir.cfg.addLastInCodeOrder(yieldpoint);
    s.remove();

    // change thread switch instruction into call to thread switch routine
    // NOTE: must make s the call instruction: it is the GC point!
    //       must also inform the GCMap that s has been moved!!!
    Offset offset = meth.getOffset();
    LocationOperand loc = new LocationOperand(offset);
    Operand guard = TG();
    Operand target = MemoryOperand.D(Magic.getTocPointer().plus(offset), (byte) 4, loc, guard);
    MIR_Call.mutate0(s, CALL_SAVE_VOLATILE, null, null, target, MethodOperand.STATIC(meth));
    yieldpoint.appendInstruction(s);
    ir.MIRInfo.gcIRMap.moveToEnd(s);

    yieldpoint.appendInstruction(MIR_Branch.create(IA32_JMP, nextBlock.makeJumpTarget()));

    // make a jump to yield block
    thisBlock.appendInstruction(MIR_Branch.create(IA32_JMP, yieldpoint.makeJumpTarget()));
  }
}
