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

import org.jikesrvm.VM;
import org.jikesrvm.classloader.BytecodeConstants;
import org.jikesrvm.classloader.BytecodeStream;
import org.jikesrvm.classloader.NormalMethod;

/**
 * Profile data for all conditional branches (including switches)
 * of a single RVMMethod.
 */
public final class BranchProfiles implements BytecodeConstants {
  private final NormalMethod method;
  private final int numCounters;
  private final BranchProfile[] data;

  /**
   * Find the BranchProfile for a given bytecode index in the BranchProfile array
   * @param bcIndex the bytecode index of the branch instruction
   * @return the desired BranchProfile, or null if it cannot be found.
   */
  public BranchProfile getEntry(int bcIndex) {
    int low = 0;
    int high = data.length - 1;
    while (true) {
      int mid = (low + high) >> 1;
      int bci = data[mid].getBytecodeIndex();
      if (bci == bcIndex) {
        return data[mid];
      }
      if (low >= high) {
        // search failed
        if (VM.VerifyAssertions) { VM._assert(false); }
        return null;
      }
      if (bci > bcIndex) {
        high = mid - 1;
      } else {
        low = mid + 1;
      }
    }
  }

  public void print(java.io.PrintStream ps) {
    ps.println("M " + numCounters + " " + method.getMemberRef());
    for (BranchProfile profile : data) {
      ps.println("\t" + profile);
    }
  }

  BranchProfiles(NormalMethod m, int[] cs) {
    method = m;
    numCounters = cs.length;

    // Originally we only allocate half of the number of edges for branch
    // profiles, like data = new BranchProfile[cs.length/2]
    // The conditional branch, tableswitch and lookupswitch all have at
    // least two edges, supposingly. Then we found that the lookupswitch
    // bytecode could have only one edge, so the number of branch profiles
    // is not necessarily less than half of the number of edges.
    BranchProfile[] data = new BranchProfile[cs.length];
    BytecodeStream bcodes = m.getBytecodes();
    int dataIdx = 0;
    int countIdx = 0;

    // We didn't record the bytecode index in the profile data to record space.
    // Therefore we must now recover that information.
    // We exploit the fact that the baseline compiler generates code in
    // a linear pass over the bytecodes to make this possible.
    while (bcodes.hasMoreBytecodes()) {
      int bcIndex = bcodes.index();
      int code = bcodes.nextInstruction();
      switch (code) {
        case JBC_ifeq:
        case JBC_ifne:
        case JBC_iflt:
        case JBC_ifge:
        case JBC_ifgt:
        case JBC_ifle:
        case JBC_if_icmpeq:
        case JBC_if_icmpne:
        case JBC_if_icmplt:
        case JBC_if_icmpge:
        case JBC_if_icmpgt:
        case JBC_if_icmple:
        case JBC_if_acmpeq:
        case JBC_if_acmpne:
        case JBC_ifnull:
        case JBC_ifnonnull: {
          int yea = cs[countIdx + EdgeCounts.TAKEN];
          int nea = cs[countIdx + EdgeCounts.NOT_TAKEN];
          int offset = bcodes.getBranchOffset();
          boolean backwards = offset < 0;
          countIdx += 2;
          data[dataIdx++] = new ConditionalBranchProfile(bcIndex, yea, nea, backwards);
          break;
        }

        case JBC_tableswitch: {
          bcodes.alignSwitch();
          bcodes.getDefaultSwitchOffset();
          int low = bcodes.getLowSwitchValue();
          int high = bcodes.getHighSwitchValue();
          int n = high - low + 1;
          data[dataIdx++] = new SwitchBranchProfile(bcIndex, cs, countIdx, n + 1);
          countIdx += n + 1;
          bcodes.skipTableSwitchOffsets(n);
          break;
        }

        case JBC_lookupswitch: {
          bcodes.alignSwitch();
          bcodes.getDefaultSwitchOffset();
          int numPairs = bcodes.getSwitchLength();
          data[dataIdx++] = new SwitchBranchProfile(bcIndex, cs, countIdx, numPairs + 1);
          countIdx += numPairs + 1;
          bcodes.skipLookupSwitchPairs(numPairs);
          break;
        }

        default:
          bcodes.skipInstruction();
          break;
      }
    }

    // Make sure we are in sync
    if (VM.VerifyAssertions) VM._assert(countIdx == cs.length);

    if (dataIdx != data.length) {
      // We had a switch statment; shrink the array.
      BranchProfile[] newData = new BranchProfile[dataIdx];
      for (int i = 0; i < dataIdx; i++) {
        newData[i] = data[i];
      }
      data = newData;
    }
    this.data = data;
  }
}
