/*
 *  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.regalloc.ppc;

import org.jikesrvm.ArchitectureSpecific;
import org.jikesrvm.VM;
import org.jikesrvm.classloader.TypeReference;
import org.jikesrvm.compilers.opt.DefUse;
import org.jikesrvm.compilers.opt.ir.Call;
import org.jikesrvm.compilers.opt.ir.Load;
import org.jikesrvm.compilers.opt.ir.MIR_Call;
import org.jikesrvm.compilers.opt.ir.MIR_Load;
import org.jikesrvm.compilers.opt.ir.MIR_Move;
import org.jikesrvm.compilers.opt.ir.MIR_Return;
import org.jikesrvm.compilers.opt.ir.MIR_Store;
import org.jikesrvm.compilers.opt.ir.Move;
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.DOUBLE_MOVE;
import static org.jikesrvm.compilers.opt.ir.Operators.FLOAT_MOVE;
import static org.jikesrvm.compilers.opt.ir.Operators.INT_MOVE;
import static org.jikesrvm.compilers.opt.ir.Operators.INT_STORE;
import static org.jikesrvm.compilers.opt.ir.Operators.IR_PROLOGUE;
import static org.jikesrvm.compilers.opt.ir.Operators.LONG_MOVE;
import static org.jikesrvm.compilers.opt.ir.Operators.PPC_FMR;
import static org.jikesrvm.compilers.opt.ir.Operators.PPC_LAddr;
import static org.jikesrvm.compilers.opt.ir.Operators.PPC_LFD;
import static org.jikesrvm.compilers.opt.ir.Operators.PPC_LFS;
import static org.jikesrvm.compilers.opt.ir.Operators.PPC_LInt;
import static org.jikesrvm.compilers.opt.ir.Operators.PPC_MOVE;
import static org.jikesrvm.compilers.opt.ir.Operators.PPC_STAddr;
import static org.jikesrvm.compilers.opt.ir.Operators.PPC_STFD;
import static org.jikesrvm.compilers.opt.ir.Operators.PPC_STFS;
import static org.jikesrvm.compilers.opt.ir.Operators.PPC_STW;
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.SYSCALL;
import org.jikesrvm.compilers.opt.ir.Register;
import org.jikesrvm.compilers.opt.ir.Prologue;
import org.jikesrvm.compilers.opt.ir.Store;
import org.jikesrvm.compilers.opt.ir.operand.DoubleConstantOperand;
import org.jikesrvm.compilers.opt.ir.operand.FloatConstantOperand;
import org.jikesrvm.compilers.opt.ir.operand.LongConstantOperand;
import org.jikesrvm.compilers.opt.ir.operand.Operand;
import org.jikesrvm.compilers.opt.ir.operand.RegisterOperand;
import org.jikesrvm.compilers.opt.ir.ppc.PhysicalRegisterSet;
import static org.jikesrvm.compilers.opt.regalloc.ppc.PhysicalRegisterConstants.BYTES_IN_ADDRESS;
import static org.jikesrvm.compilers.opt.regalloc.ppc.PhysicalRegisterConstants.BYTES_IN_DOUBLE;
import static org.jikesrvm.compilers.opt.regalloc.ppc.PhysicalRegisterConstants.BYTES_IN_FLOAT;
import static org.jikesrvm.compilers.opt.regalloc.ppc.PhysicalRegisterConstants.BYTES_IN_INT;
import static org.jikesrvm.compilers.opt.regalloc.ppc.PhysicalRegisterConstants.FIRST_DOUBLE_PARAM;
import static org.jikesrvm.compilers.opt.regalloc.ppc.PhysicalRegisterConstants.FIRST_DOUBLE_RETURN;
import static org.jikesrvm.compilers.opt.regalloc.ppc.PhysicalRegisterConstants.FIRST_INT_PARAM;
import static org.jikesrvm.compilers.opt.regalloc.ppc.PhysicalRegisterConstants.FIRST_INT_RETURN;
import static org.jikesrvm.compilers.opt.regalloc.ppc.PhysicalRegisterConstants.LOG_BYTES_IN_ADDRESS;
import static org.jikesrvm.compilers.opt.regalloc.ppc.PhysicalRegisterConstants.NUMBER_DOUBLE_PARAM;
import static org.jikesrvm.compilers.opt.regalloc.ppc.PhysicalRegisterConstants.NUMBER_INT_PARAM;
import static org.jikesrvm.ppc.StackframeLayoutConstants.STACKFRAME_HEADER_SIZE;
import static org.jikesrvm.ppc.StackframeLayoutConstants.STACKFRAME_METHOD_ID_OFFSET;
import org.vmmagic.unboxed.Offset;

/**
 * This class contains PowerPC Calling conventions.
 * The two public methods are:
 * <ul>
 *  <li> expandCallingConventions(IR) which is called by
 *      the register allocator immediately before allocation to make
 *      manifest the use of registers by the calling convention.
 *  <li> expandSysCall(Instruction, IR) which is called to
 *      expand a SYSCALL HIR instruction into the appropriate
 *      sequence of LIR instructions.
 *  </ul>
 */
public abstract class CallingConvention extends IRTools {

  /**
   * Expand calls, returns, and add initialize code for arguments/parms.
   * @param ir
   */
  public static void expandCallingConventions(IR ir) {
    for (Instruction inst = ir.firstInstructionInCodeOrder(); inst != null; inst =
        inst.nextInstructionInCodeOrder()) {
      if (inst.isCall()) {
        callExpand(inst, ir);
      } else if (inst.isReturn()) {
        returnExpand(inst, ir);
      }
    }
    prologueExpand(ir);
  }

  /**
   * This is just called for instructions that were added by
   * instrumentation during register allocation
   */
  public static void expandCallingConventionsForInstrumentation(IR ir, Instruction from, Instruction to) {
    for (Instruction inst = from; inst != to; inst = inst.nextInstructionInCodeOrder()) {
      if (inst.isCall()) {
        callExpand(inst, ir);
      } else if (inst.isReturn()) {
        returnExpand(inst, ir);
      }
    }
  }

  /**
   * Calling convention to implement calls to
   * native (C) routines using the AIX linkage conventions
   */
  public static void expandSysCall(Instruction s, IR ir) {
    RegisterOperand ip = (RegisterOperand) Call.getClearAddress(s);
    int numberParams = Call.getNumberOfParams(s);

    /* Long, float, and double constants are loaded from the JTOC.
     * We must ensure that they are loaded _before_ we change
     * the JTOC to be the C TOC so inject explicit moves to do so.
     */
    for (int i = 0; i < numberParams; i++) {
      Operand arg = Call.getParam(s, i);
      if (arg instanceof LongConstantOperand) {
        LongConstantOperand op = (LongConstantOperand) Call.getClearParam(s, i);
        RegisterOperand rop = ir.regpool.makeTempLong();
        s.insertBefore(Move.create(LONG_MOVE, rop, op));
        Call.setParam(s, i, rop.copy());
      } else if (arg instanceof DoubleConstantOperand) {
        DoubleConstantOperand op = (DoubleConstantOperand) Call.getClearParam(s, i);
        RegisterOperand rop = ir.regpool.makeTempDouble();
        s.insertBefore(Move.create(DOUBLE_MOVE, rop, op));
        Call.setParam(s, i, rop.copy());
      } else if (arg instanceof FloatConstantOperand) {
        FloatConstantOperand op = (FloatConstantOperand) Call.getClearParam(s, i);
        RegisterOperand rop = ir.regpool.makeTempFloat();
        s.insertBefore(Move.create(FLOAT_MOVE, rop, op));
        Call.setParam(s, i, rop.copy());
      }
    }

    /* compute the parameter space */
    int parameterWords;
    if (VM.BuildFor32Addr) {
      parameterWords = 0;
      for (int i = 0; i < numberParams; i++) {
        parameterWords++;
        Operand op = Call.getParam(s, i);
        if (op instanceof RegisterOperand) {
          RegisterOperand reg = (RegisterOperand) op;
          if (reg.getType().isLongType() || reg.getType().isDoubleType()) {
            parameterWords++;
          }
        }
      }
    } else {
      parameterWords = numberParams;
    }
    // see PowerPC Compiler Writer's Guide, pp. 162
    ir.stackManager.allocateParameterSpace((6 + parameterWords) * BYTES_IN_ADDRESS);
    // IMPORTANT WARNING: as the callee C routine may destroy the cmid field
    // (it is the saved CR field of the callee in C convention)
    // we are restoring the methodID after a sysCall.
    Instruction s2 =
        Store.create(REF_STORE,
                     ir.regpool.makeJTOCOp(ir, s),
                     ir.regpool.makeFPOp(),
                     AC(Offset.fromIntSignExtend(5 * BYTES_IN_ADDRESS)),
                     null);         // TODO: valid location?
    s.insertBefore(s2);
    if (VM.BuildForPowerOpenABI) {
      s2 =
          Load.create(REF_LOAD, ir.regpool.makeJTOCOp(ir, s), ip, AC(Offset.fromIntZeroExtend(BYTES_IN_ADDRESS)), null);
      s.insertBefore(s2);
      RegisterOperand iptmp = ir.regpool.makeTempAddress();
      s2 = Load.create(REF_LOAD, iptmp, ip, AC(Offset.zero()), null);
      s.insertBefore(s2);
      ip = iptmp;
    }
    Call.mutate0(s, SYSCALL, Call.getClearResult(s), ip, null);
    s2 =
        Load.create(REF_LOAD,
                    ir.regpool.makeJTOCOp(ir, s),
                    ir.regpool.makeFPOp(),
                    AC(Offset.fromIntSignExtend(5 * BYTES_IN_ADDRESS)),
                    null);         // TODO: valid location?
    s.insertAfter(s2);
    RegisterOperand temp = ir.regpool.makeTempInt();
    s2 = Move.create(INT_MOVE, temp, IC(ir.compiledMethod.getId()));
    Instruction s3 =
        Store.create(INT_STORE,
                     temp.copy(),
                     ir.regpool.makeFPOp(),
                     AC(Offset.fromIntSignExtend(STACKFRAME_METHOD_ID_OFFSET)),
                     null);  // TODO: valid location?
    s.insertAfter(s3);
    s.insertAfter(s2);
  }

  /////////////////////
  // Implementation
  /////////////////////

  /**
   * Expand the prologue instruction to make calling convention explicit.
   */
  private static void prologueExpand(IR ir) {

    // set up register lists for dead code elimination.
    boolean useDU = ir.options.getOptLevel() >= 1;
    if (useDU) {
      DefUse.computeDU(ir);
    }

    Instruction prologueInstr = ir.firstInstructionInCodeOrder().nextInstructionInCodeOrder();
    if (VM.VerifyAssertions) VM._assert(prologueInstr.operator == IR_PROLOGUE);
    Instruction start = prologueInstr.nextInstructionInCodeOrder();

    int int_index = 0;
    int double_index = 0;
    int spilledArgumentCounter =
        (-256 - ArchitectureSpecific.ArchConstants.STACKFRAME_HEADER_SIZE) >> LOG_BYTES_IN_ADDRESS;
    PhysicalRegisterSet phys = ir.regpool.getPhysicalRegisterSet();
    Register FP = phys.getFP();
    for (OperandEnumeration symParams = prologueInstr.getDefs(); symParams.hasMoreElements();) {
      RegisterOperand symParamOp = (RegisterOperand) symParams.next();
      Register symParam = symParamOp.getRegister();
      TypeReference t = symParamOp.getType();
      if (t.isFloatType()) {
        // if optimizing, skip dead parameters
        // SJF: This optimization current breaks the paranoid sanity test.
        // Why? TODO: figure this out and remove the 'true' case below
        if (true || !useDU || symParam.useList != null) {
          if (double_index < NUMBER_DOUBLE_PARAM) {
            Register param = phys.get(FIRST_DOUBLE_PARAM + (double_index));
            start.insertBefore(MIR_Move.create(PPC_FMR, F(symParam), F(param)));
          } else {                  // spilled parameter
            start.insertBefore(MIR_Load.create(PPC_LFS,
                                               F(symParam),
                                               A(FP),
                                               IC((spilledArgumentCounter << LOG_BYTES_IN_ADDRESS) - BYTES_IN_ADDRESS +
                                                  BYTES_IN_FLOAT)));
            spilledArgumentCounter--;
          }
        }
        double_index++;
      } else if (t.isDoubleType()) {
        // if optimizing, skip dead parameters
        // SJF: This optimization current breaks the paranoid sanity test.
        // Why? TODO: figure this out and remove the 'true' case below
        if (true || !useDU || symParam.useList != null) {
          if (double_index < NUMBER_DOUBLE_PARAM) {
            Register param = phys.get(FIRST_DOUBLE_PARAM + (double_index));
            start.insertBefore(MIR_Move.create(PPC_FMR, D(symParam), D(param)));
          } else {                  // spilled parameter
            start.insertBefore(MIR_Load.create(PPC_LFD,
                                               D(symParam),
                                               A(FP),
                                               IC(spilledArgumentCounter << LOG_BYTES_IN_ADDRESS)));
            spilledArgumentCounter -= BYTES_IN_DOUBLE / BYTES_IN_ADDRESS;
          }
        }
        double_index++;
      } else { // t is object, 1/2 of a long, int, short, char, byte, or boolean
        // if optimizing, skip dead parameters
        // SJF: This optimization current breaks the paranoid sanity test.
        // Why? TODO: figure this out and remove the 'true' case below
        if (true || !useDU || symParam.useList != null) {
          if (int_index < NUMBER_INT_PARAM) {
            Register param = phys.get(FIRST_INT_PARAM + (int_index));
            start.insertBefore(MIR_Move.create(PPC_MOVE, new RegisterOperand(symParam, t), A(param)));
          } else {                  // spilled parameter
            if (VM
                .BuildFor64Addr &&
                                (t.isIntType() ||
                                 t.isShortType() ||
                                 t.isByteType() ||
                                 t.isCharType() ||
                                 t.isBooleanType())) {
              start.insertBefore(MIR_Load.create(PPC_LInt,
                                                 new RegisterOperand(symParam, t),
                                                 A(FP),
                                                 IC((spilledArgumentCounter << LOG_BYTES_IN_ADDRESS) -
                                                    BYTES_IN_ADDRESS + BYTES_IN_INT)));
            } else {
              // same size as addr (ie, either we're in 32 bit mode or we're in 64 bit mode and it's a reference or long)
              start.insertBefore(MIR_Load.create(PPC_LAddr,
                                                 new RegisterOperand(symParam, t),
                                                 A(FP),
                                                 IC(spilledArgumentCounter << LOG_BYTES_IN_ADDRESS)));
            }
            spilledArgumentCounter--;
          }
        }
        int_index++;
      }
    }

    // Now that we've made the calling convention explicit in the prologue,
    // set IR_PROLOGUE to have no defs.
    prologueInstr.replace(Prologue.create(IR_PROLOGUE, 0));
  }

  /**
   * Expand the call as appropriate
   * @param s the call instruction
   * @param ir the ir
   */
  private static void callExpand(Instruction s, IR ir) {
    int NumberParams = MIR_Call.getNumberOfParams(s);
    int int_index = 0;          // points to the first integer volatile
    int double_index = 0;       // poinst to the first f.p.    volatile
    int callSpillLoc = STACKFRAME_HEADER_SIZE;
    PhysicalRegisterSet phys = ir.regpool.getPhysicalRegisterSet();
    Instruction prev = s.prevInstructionInCodeOrder();
    Register FP = phys.getFP();
    boolean isSysCall = ir.stackManager.isSysCall(s);
    boolean firstLongHalf = false;

    // (1) Expand parameters
    for (int opNum = 0; opNum < NumberParams; opNum++) {
      Operand param = MIR_Call.getClearParam(s, opNum);
      RegisterOperand Reg = (RegisterOperand) param;
      // as part of getting into MIR, we make sure all params are in registers.
      Register reg = Reg.getRegister();
      if (Reg.getType().isFloatType()) {
        if (double_index < NUMBER_DOUBLE_PARAM) {       // register copy
          Register real = phys.get(FIRST_DOUBLE_PARAM + (double_index++));
          s.insertBefore(MIR_Move.create(PPC_FMR, F(real), Reg));
          Reg = F(real);
          // Record that the call now has a use of the real reg
          // This is to ensure liveness is correct
          MIR_Call.setParam(s, opNum, Reg);
        } else {                  // spill to memory
          Instruction p = prev.nextInstructionInCodeOrder();
          callSpillLoc += BYTES_IN_ADDRESS;
          p.insertBefore(MIR_Store.create(PPC_STFS, F(reg), A(FP), IC(callSpillLoc - BYTES_IN_FLOAT)));
          // We don't have uses of the heap at MIR, so null it out
          MIR_Call.setParam(s, opNum, null);
        }
      } else if (Reg.getType().isDoubleType()) {
        if (double_index < NUMBER_DOUBLE_PARAM) {     // register copy
          Register real = phys.get(FIRST_DOUBLE_PARAM + (double_index++));
          s.insertBefore(MIR_Move.create(PPC_FMR, D(real), Reg));
          Reg = D(real);
          // Record that the call now has a use of the real reg
          // This is to ensure liveness is correct
          MIR_Call.setParam(s, opNum, Reg);
        } else {                  // spill to memory
          Instruction p = prev.nextInstructionInCodeOrder();
          p.insertBefore(MIR_Store.create(PPC_STFD, D(reg), A(FP), IC(callSpillLoc)));
          callSpillLoc += BYTES_IN_DOUBLE;
          // We don't have uses of the heap at MIR, so null it out
          MIR_Call.setParam(s, opNum, null);
        }
      } else {                    // IntType (or half of long) or reference
        if (VM.BuildForSVR4ABI) {
          /* NOTE: following adjustment is not stated in SVR4 ABI, but
           * was implemented in GCC.
           */
          if (isSysCall && Reg.getType().isLongType()) {
            if (firstLongHalf) {
              firstLongHalf = false;
            } else {
              int true_index = FIRST_INT_PARAM + int_index;
              int_index += (true_index + 1) & 0x01; // if gpr is even, gpr += 1
              firstLongHalf = true;
            }
          }
        }
        if (int_index < NUMBER_INT_PARAM) {             // register copy
          Register real = phys.get(FIRST_INT_PARAM + (int_index++));
          RegisterOperand Real = new RegisterOperand(real, Reg.getType());
          s.insertBefore(MIR_Move.create(PPC_MOVE, Real, Reg));
          Reg = new RegisterOperand(real, Reg.getType());
          // Record that the call now has a use of the real reg
          // This is to ensure liveness is correct
          MIR_Call.setParam(s, opNum, Reg);
        } else {                  // spill to memory
          Instruction p = prev.nextInstructionInCodeOrder();
          callSpillLoc += BYTES_IN_ADDRESS;
          if (VM
              .BuildFor64Addr &&
                              (Reg.getType().isIntType() ||
                               Reg.getType().isShortType() ||
                               Reg.getType().isByteType() ||
                               Reg.getType().isCharType() ||
                               Reg.getType().isBooleanType())) {
            p.insertBefore(MIR_Store.create(PPC_STW,
                                            new RegisterOperand(reg, Reg.getType()),
                                            A(FP),
                                            IC(callSpillLoc - BYTES_IN_INT)));
          } else {
            // same size as addr (ie, either we're in 32 bit mode or we're in 64 bit mode and it's a reference or long)
            p.insertBefore(MIR_Store.create(PPC_STAddr,
                                            new RegisterOperand(reg, Reg.getType()),
                                            A(FP),
                                            IC(callSpillLoc - BYTES_IN_ADDRESS)));
          }
          // We don't have uses of the heap at MIR, so null it out
          MIR_Call.setParam(s, opNum, null);
        }
      }
    }
    // If we needed to pass arguments on the stack,
    // then make sure we have a big enough stack
    if (callSpillLoc != STACKFRAME_HEADER_SIZE) {
      ir.stackManager.allocateParameterSpace(callSpillLoc);
    }
    // (2) expand result
    Instruction lastCallSeqInstr = s;
    if (MIR_Call.hasResult2(s)) {
      if (VM.VerifyAssertions) VM._assert(VM.BuildFor32Addr);
      RegisterOperand result2 = MIR_Call.getClearResult2(s);
      RegisterOperand physical = new RegisterOperand(phys.get(FIRST_INT_RETURN + 1), result2.getType());
      Instruction tmp = MIR_Move.create(PPC_MOVE, result2, physical);
      lastCallSeqInstr.insertAfter(tmp);
      lastCallSeqInstr = tmp;
      MIR_Call.setResult2(s, null);
    }
    if (MIR_Call.hasResult(s)) {
      RegisterOperand result1 = MIR_Call.getClearResult(s);
      if (result1.getType().isFloatType() || result1.getType().isDoubleType()) {
        RegisterOperand physical = new RegisterOperand(phys.get(FIRST_DOUBLE_RETURN), result1.getType());
        Instruction tmp = MIR_Move.create(PPC_FMR, result1, physical);
        lastCallSeqInstr.insertAfter(tmp);
        lastCallSeqInstr = tmp;
        MIR_Call.setResult(s, null);
      } else {
        RegisterOperand physical = new RegisterOperand(phys.get(FIRST_INT_RETURN), result1.getType());
        Instruction tmp = MIR_Move.create(PPC_MOVE, result1, physical);
        lastCallSeqInstr.insertAfter(tmp);
        lastCallSeqInstr = tmp;
        MIR_Call.setResult(s, null);
      }
    }
  }

  /**
   * Expand return statements.
   * @param s the return instruction
   * @param ir the ir
   */
  private static void returnExpand(Instruction s, IR ir) {
    PhysicalRegisterSet phys = ir.regpool.getPhysicalRegisterSet();
    if (MIR_Return.hasVal(s)) {
      RegisterOperand symb1 = MIR_Return.getClearVal(s);
      RegisterOperand phys1;
      if (symb1.getType().isFloatType() || symb1.getType().isDoubleType()) {
        phys1 = D(phys.get(FIRST_DOUBLE_RETURN));
        s.insertBefore(MIR_Move.create(PPC_FMR, phys1, symb1));
      } else {
        phys1 = new RegisterOperand(phys.get(FIRST_INT_RETURN), symb1.getType());
        s.insertBefore(MIR_Move.create(PPC_MOVE, phys1, symb1));
      }
      MIR_Return.setVal(s, phys1.copyD2U());
    }
    if (MIR_Return.hasVal2(s)) {
      if (VM.VerifyAssertions) VM._assert(VM.BuildFor32Addr);
      RegisterOperand symb2 = MIR_Return.getClearVal2(s);
      RegisterOperand phys2 = I(phys.get(FIRST_INT_RETURN + 1));
      s.insertBefore(MIR_Move.create(PPC_MOVE, phys2, symb2));
      MIR_Return.setVal2(s, phys2.copyD2U());
    }
  }

  /**
   * Save and restore all nonvolatile registers around a syscall.
   * On PPC, our register conventions are compatablile with the
   * natvie ABI, so there is nothing to do.
   *
   * @param call the sys call
   */
  public static void saveNonvolatilesAroundSysCall(Instruction call, IR ir) {
  }

}
