/*
 *  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.adaptive.measurements.instrumentation;

import org.jikesrvm.VM;
import org.jikesrvm.adaptive.AosEntrypoints;
import org.jikesrvm.classloader.TypeReference;
import org.jikesrvm.compilers.opt.InstrumentedEventCounterManager;
import org.jikesrvm.compilers.opt.driver.OptConstants;
import org.jikesrvm.compilers.opt.hir2lir.ConvertToLowLevelIR;
import org.jikesrvm.compilers.opt.ir.ALoad;
import org.jikesrvm.compilers.opt.ir.AStore;
import org.jikesrvm.compilers.opt.ir.InstrumentedCounter;
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.Operator;
import org.jikesrvm.compilers.opt.ir.Operators;
import org.jikesrvm.compilers.opt.ir.operand.DoubleConstantOperand;
import org.jikesrvm.compilers.opt.ir.operand.IntConstantOperand;
import org.jikesrvm.compilers.opt.ir.operand.Operand;
import org.jikesrvm.compilers.opt.ir.operand.RegisterOperand;
import org.vmmagic.unboxed.Offset;

/**
 * An implementation of a InstrumentedEventCounterManager .  It
 * uses an unsynchronized two dimensional array of doubles to allocate
 * its counters. (see InstrumentedEventCounterManager.java for a
 * description of a counter manager)
 *
 * NOTE: Much of this class was stolen from CounterArray.java, which
 * is now gone.
 */
public final class CounterArrayManager extends InstrumentedEventCounterManager implements Operators, OptConstants {

  static final boolean DEBUG = false;

  /**
   *  This method is called my a ManagedData object to obtain space
   *  in the counter manager.  A handle or "ID" is returned for the
   *  data to identify its counter space.
   *
   * @param countersNeeded The number of counters being requested
   * @return The handle for this data's counter space.
   **/
  public synchronized int registerCounterSpace(int countersNeeded) {
    if (counterArrays.length == numCounterArrays) {
      expandCounterArrays();
    }

    // return the handle of the next available counter array
    int handle = numCounterArrays;

    // resize the appropriate counter array
    resizeCounterSpace(handle, countersNeeded);

    numCounterArrays++;

    return handle;
  }

  /**
   *  This method is called to change the number of counters needed by
   *  a particular data.
   *
   * @param handle  The handle describing which the data to be resized
   * @param countersNeeded The number of counters being requested
   **/
  public synchronized void resizeCounterSpace(int handle, int countersNeeded) {
    // allocate the new array
    double[] temp = new double[countersNeeded];

    // transfer the old data to the new array
    if (counterArrays[handle] != null) {
      for (int i = 0; i < counterArrays[handle].length; i++) {
        temp[i] = counterArrays[handle][i];
      }
    }

    // switch to the new counter array
    counterArrays[handle] = temp;
  }

  /**
   * Return the value of a particular counter
   *
   * @param handle The handle describing which the data to look in
   * @param index The relative index number of the counter
   * @return The value of the counter
   */
  public double getCounter(int handle, int index) {
    return counterArrays[handle][index];
  }

  /**
   * Set the value of a particular counter
   *
   * @param handle The handle describing which the data to look in
   * @param index The relative index number of the counter
   * @param value The new value of the counter
   */
  public void setCounter(int handle, int index, double value) {
    counterArrays[handle][index] = value;
  }

  /**
   * Create a place holder instruction to represent the counted event.
   *
   * @param handle  The handle of the array for the method
   * @param index   Index within that array
   * @param incrementValue The value to add to the counter
   * @return The counter instruction
   **/
  public Instruction createEventCounterInstruction(int handle, int index, double incrementValue) {
    // Now create the instruction to be returned.
    Instruction c =
        InstrumentedCounter.create(INSTRUMENTED_EVENT_COUNTER,
                                   new IntConstantOperand(handle),
                                   new IntConstantOperand(index),
                                   new DoubleConstantOperand(incrementValue, Offset.zero()));
    c.bcIndex = INSTRUMENTATION_BCI;

    return c;
  }

  /**
   *  Take an event counter instruction and mutate it into IR
   *  instructions that will do the actual counting.
   *
   *  Precondition: IR is in LIR
   *
   * @param counterInst   The counter instruction to mutate
   * @param ir            The governing IR
   **/
  public void mutateOptEventCounterInstruction(Instruction counterInst, IR ir) {
    if (VM.VerifyAssertions) {
      VM._assert(InstrumentedCounter.conforms(counterInst));
    }

    IntConstantOperand intOp = InstrumentedCounter.getData(counterInst);
    int handle = intOp.value;
    intOp = InstrumentedCounter.getIndex(counterInst);
    int index = intOp.value;

    // Get the base of array
    RegisterOperand counterArray = ConvertToLowLevelIR.
        getStatic(counterInst, ir, AosEntrypoints.counterArrayManagerCounterArraysField);

    // load counterArrays[handle]
    RegisterOperand array2 =
        InsertALoadOffset(counterInst, ir, REF_ALOAD, TypeReference.JavaLangObject, counterArray, handle);
    ConvertToLowLevelIR.
        doArrayLoad(counterInst.prevInstructionInCodeOrder(), ir, INT_LOAD, 2);

    // load counterArrays[handle][index]
    RegisterOperand origVal =
        InsertALoadOffset(counterInst, ir, DOUBLE_ALOAD, TypeReference.Double, array2, index);
    ConvertToLowLevelIR.
        doArrayLoad(counterInst.prevInstructionInCodeOrder(), ir, DOUBLE_LOAD, 3);

    Operand incOperand = InstrumentedCounter.getIncrement(counterInst);
    // Insert increment instruction
    RegisterOperand newValue =
        ConvertToLowLevelIR.insertBinary(counterInst,
                                             ir,
                                             DOUBLE_ADD,
                                             TypeReference.Double,
                                             origVal,
                                             incOperand.copy());

    // Store it
    Instruction store =
        AStore.mutate(counterInst, DOUBLE_ASTORE, newValue, array2.copyU2D(), IRTools.IC(index), null, null);
    ConvertToLowLevelIR.doArrayStore(store, ir, DOUBLE_STORE, 3);

  }

  /**
   * Insert array 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 InsertALoadOffset(Instruction s, IR ir, Operator operator,
                                               TypeReference type, Operand reg2, int offset) {
    RegisterOperand regTarget = ir.regpool.makeTemp(type);
    Instruction s2 = ALoad.create(operator, regTarget, reg2, IRTools.IC(offset), null, null);
    s.insertBefore(s2);
    return regTarget.copyD2U();
  }

  /**
   * Still  under construction.
   */
  public void insertBaselineCounter() {
  }

  /**
   * decay counters
   *
   * @param handle  The identifier of the counter array to decay
   * @param rate    The rate at which to decay, i.e. a value of 2 will divide
   *                all values in half
   */
  static void decay(int handle, double rate) {
    int len = counterArrays[handle].length;
    for (int i = 0; i < len; i++) {
      counterArrays[handle][i] /= rate;
    }
  }

  /** Implementation */
  static final int INITIAL_COUNT = 10;
  static final int INCREMENT = 10;
  static int numCounterArrays = 0;
  static double[][] counterArrays = new double[INITIAL_COUNT][];

  /**
   * increment the number of counter arrays
   */
  private static void expandCounterArrays() {
    // expand the number of counter arrays
    double[][] temp = new double[counterArrays.length * 2][];

    // transfer the old counter arrays to the new storage
    for (int i = 0; i < counterArrays.length; i++) {
      temp[i] = counterArrays[i];
    }
    counterArrays = temp;
  }

} // end of class



