/*
 *  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.mmtk.utility.deque;

import org.mmtk.policy.RawPageSpace;

import org.mmtk.vm.VM;

import org.vmmagic.pragma.*;
import org.vmmagic.unboxed.*;

/**
 * This supports <i>unsynchronized</i> enqueuing and dequeuing of buffers
 * for shared use.  The data can be added to and removed from either end
 * of the deque. This class is based upon the SharedQueue class.  The sorting
 * routines were modified from code written by Narendran Sachindran and
 * Matthew Hertz for GCTk.
 */
@Uninterruptible public abstract class SortSharedDeque extends SharedDeque {


  private static final int BYTES_PUSHED = BYTES_IN_ADDRESS * 5;
  private static final int MAX_STACK_SIZE = BYTES_PUSHED * 64;
  private static final Offset INSERTION_SORT_LIMIT = Offset.fromIntSignExtend(80);

  /***********************************************************************
   *
   * Class variables
   */

  /**
   * Constructor
   *
   * @param rps The space from which the instance should obtain buffers.
   */
  public SortSharedDeque(String name, RawPageSpace rps, int arity) {
    super(name, rps, arity);
    stackBase = AddressArray.create(MAX_STACK_SIZE);
    stackLoc = 0;
  }

  /***********************************************************************
   *
   * Sorting methods, utilities, and instance variables
   */


  /**
   * Return the sorting key for the object passed as a parameter.
   *
   * @param obj The address of the object whose key is wanted
   * @return The value of the sorting key for this object
   */
  protected abstract Word getKey(Address obj);

  private static final Word mask16 = Word.fromIntZeroExtend(0xffff0000);
  private static final Word mask8 = Word.fromIntZeroExtend(0x0000ff00);
  private static final Word mask4 = Word.fromIntZeroExtend(0x000000f0);
  private static final Word mask2 = Word.fromIntZeroExtend(0x0000000c);
  private static final Word mask1 = Word.fromIntZeroExtend(0x00000002);

  /**
   * Find the highest bit that is set in a longword and return a mask
   * with only that bit set.
   *
   * @param addr Value for which the mask needs to be found
   * @return The highest bit set in the parameter
   */
  private static Word getBitMask(Word addr) {
    int shift = 0;
    if (!addr.and(mask16).isZero()) {
      addr = addr.rshl(16);
      shift += 16;
    }
    if (!addr.and(mask8).isZero()) {
      addr = addr.rshl(8);
      shift += 8;
    }
    if (!addr.and(mask4).isZero()) {
      addr = addr.rshl(4);
      shift += 4;
    }
    if (!addr.and(mask2).isZero()) {
      addr = addr.rshl(2);
      shift += 2;
    }
    if (!addr.and(mask1).isZero()) {
      shift += 1;
    }
    return (Word.one().lsh(shift));
  }

  /**
   * Perform insertion sort within an intra-block address range.
   *
   *  @param begin Start address of the range to be sorted
   *  @param end End address of the range to be sorted
   */
  private void insertionSort(Address begin, Address end) {
    Address rPtr = begin.minus(BYTES_IN_ADDRESS);
    Address lPtr;

    while (rPtr.GE(end)) {
      Address rSlot = rPtr.loadAddress();
      Word rKey = getKey(rSlot);
      lPtr = rPtr.plus(BYTES_IN_ADDRESS);
      while (lPtr.LE(begin)) {
        Address lSlot = lPtr.loadAddress();
        Word lKey = getKey(lSlot);
        if (lKey.GT(rKey)) {
          lPtr.minus(BYTES_IN_ADDRESS).store(lSlot);
          lPtr = lPtr.plus(BYTES_IN_ADDRESS);
        } else {
          break;
        }
      }
      lPtr.minus(BYTES_IN_ADDRESS).store(rSlot);
      rPtr = rPtr.minus(BYTES_IN_ADDRESS);
    }
  }

  /**
   * Sort objects using radix exchange sort. An explicit stack is
   *  maintained to avoid using recursion.
   */
  public void sort() {
    Address startPtr, startLink, endPtr, endLink;
    Word bitMask;
    if (!head.EQ(HEAD_INITIAL_VALUE)) {
      if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(tail.NE(TAIL_INITIAL_VALUE));
      /* Obtain the bitmask for the first iteration and save the start &
         end pointers and the bitmask on the stack */
      initStack();
      startPtr = popStack();
      while (!startPtr.isZero()) {
        startLink = popStack();
        endPtr = popStack();
        endLink = popStack();
        bitMask = (popStack()).toWord();
        if (startLink.NE(endLink)) {
          partition(startPtr, startLink, endPtr, endLink, bitMask);
        } else if (startPtr.GT(endPtr)) {
          /* Use insertionSort for a limited number of objects within
             a single block */
          if (startPtr.diff(endPtr).sLT(INSERTION_SORT_LIMIT)) {
            insertionSort(startPtr, endPtr);
          } else {
            partition(startPtr, startLink, endPtr, endLink, bitMask);
          }
        }
        // Get the next set of data to sort
        startPtr = popStack();
      }
    }
    checkIfSorted();
  }

  /**
   * Partition the slots in an address range based on the value in
   * a particular bit. Place the start & end addresses of the two
   * partitions & a bitmask per partition (which indicates the highest
   * bit position at which the max & min of a partition differ) on the
   * stack. This works just like the partition in quick sort, except
   * that a bit value is being compared here
   *
   * @param startAddr The start address of the range to be sorted
   * @param startLinkAddr The address where the start block has its next field
   * @param endAddr The end address of the range to be sorted
   * @param endLinkAddr The address where the end block has its next field
   * @param bitMask The mask in which the bit to be commpared is set
   */
  private void partition(Address startAddr, Address startLinkAddr,
                               Address endAddr, Address endLinkAddr,
                               Word bitMask) {
    Address travPtr = endAddr;
    Address travLink = endLinkAddr;
    Address stopPtr = startAddr;
    Address stopLink = startLinkAddr;
    Address travSlot, stopSlot;
    Word travKey, stopKey;
    Word lmax = Word.zero(), rmax = Word.zero();
    Word lmin = Word.max(), rmin = Word.max();

    while (true) {
      /* Compute the address range within the current block to compute. */
      Address endOfBlock = travLink;

      /* Move the left pointer until the right pointer is reached
         or an address with a 0 value in the bit position is found. */
      while (true) {
        travSlot = travPtr.loadAddress();
        travKey = getKey(travSlot);

        /* If we reach the end. */
        if (travPtr.EQ(stopPtr))
          break;
        /* If we found a 0 in bit position, break. */
        if (travKey.and(bitMask).isZero())
          break;
        if (travKey.GT(rmax))
          rmax = travKey;
        if (travKey.LT(rmin))
          rmin = travKey;
        /* Move to the next entry. */
        travPtr = travPtr.plus(BYTES_IN_ADDRESS);
        /* If at end of remset block, move to next block */
        if (travPtr.EQ(endOfBlock)) {
          travLink = getPrev(travPtr);
          endOfBlock = travLink;
          travPtr = bufferStart(endOfBlock);
        }
      }

      /* Store the end of the block. */
      endOfBlock = bufferStart(stopPtr);
      /* Move the right pointer until the left pointer is reached
         or an address with a 1 value in the bit position is found. */
      while (true) {
        stopSlot = stopPtr.loadAddress();
        stopKey = getKey(stopSlot);
        /* Found a 1 in bit position, break. */
        if (!stopKey.and(bitMask).isZero())
          break;
        if (stopKey.GT(lmax))
          lmax = stopKey;
        if (stopKey.LT(lmin))
          lmin = stopKey;
        if (stopPtr.EQ(travPtr))
          break;
        /* Move to the next entry, which may be in the next block. */
        if (stopPtr.EQ(endOfBlock)) {
          stopLink = getNext(stopLink);
          stopPtr = stopLink;
          endOfBlock = bufferStart(stopPtr);
        }
        stopPtr = stopPtr.minus(BYTES_IN_ADDRESS);
      }
      if (stopPtr.EQ(travPtr))
        break;
      /* interchange the values pointed to by the left and right pointers */
      travPtr.store(stopSlot);
      stopPtr.store(travSlot);
    }

    /* If max value is not equal to the min value in the right partition,
       (not all slots are identical) push the right partition on to the stack */
    if (rmax.GT(rmin)) {
      if (travPtr.EQ(bufferStart(travPtr))) {
        stopLink = getNext(travLink);
        stopPtr = stopLink;
      } else {
        stopLink = travLink;
        stopPtr = travPtr;
      }
      pushOnStack(getBitMask(rmax.xor(rmin)).toAddress());
      pushOnStack(endLinkAddr);
      pushOnStack(endAddr);
      pushOnStack(stopLink);
      pushOnStack(stopPtr.minus(BYTES_IN_ADDRESS));
    }
    /* if max value is not equal to the min value in the left partition,
       (not all slots are identical) push the left partition on to the stack */
    if (lmax.GT(lmin)) {
      pushOnStack(getBitMask(lmax.xor(lmin)).toAddress());
      pushOnStack(travLink);
      pushOnStack(travPtr);
      pushOnStack(startLinkAddr);
      pushOnStack(startAddr);
    }
  }

  /*************************************************************************
   *
   * Sorting Stack management routines
   */
  private int stackLoc;
  private AddressArray stackBase;

  /*
   * Allocate memory for the stack and intialize it with the first range
   * to partition
   */
  private void initStack() {
    stackLoc = 0;

    Address endOfBlock = tail;
    Address startPtr = bufferStart(endOfBlock);
    Word min = Word.max();
    Word max = Word.zero();
    // Find the max. and min addresses in the object buffer
    while (endOfBlock.NE(HEAD_INITIAL_VALUE)) {
      startPtr = bufferStart(endOfBlock);
      while (startPtr.LT(endOfBlock)) {
        Address startSlot = startPtr.loadAddress();
        Word startKey = getKey(startSlot);
        if (startKey.GT(max))
          max = startKey;
        if (startKey.LT(min))
          min = startKey;
        startPtr = startPtr.plus(BYTES_IN_ADDRESS);
      }
      endOfBlock = getPrev(startPtr);
    }

    // If max and min are different (not all elements are equal), push the
    // start, end and bitmask values on the stack
    if (max.GT(min)) {
      pushOnStack(getBitMask(max.xor(min)).toAddress());
      pushOnStack(tail);
      pushOnStack(bufferStart(tail));
      pushOnStack(startPtr);
      pushOnStack(startPtr.minus(BYTES_IN_ADDRESS));
    }
  }

  /**
   * Push an address on to the stack
   *
  * @param val The address to be pushed
   */
  private void pushOnStack(Address val) {
    stackBase.set(stackLoc, val);
    stackLoc++;
  }

  /**
   * Pop an address from the stack
   *
   * @return The address at the top of the stack, or 0 if stack is empty
   */
  private Address popStack() {
    if (stackLoc == 0)
      return Address.zero();
    stackLoc--;
    return stackBase.get(stackLoc);
  }

  /**
   * Debug routine, used to check if the object buffer is sorted correctly in
   * decreasing final reference deletion time
 */
  private void checkIfSorted() {
    if (VM.VERIFY_ASSERTIONS) {
      Address buf, end;
      Word prevKey = Word.max();
      end = tail;
      buf = bufferStart(end);
      while (buf.NE(HEAD_INITIAL_VALUE)) {
        // iterate through the block
        while (buf.LT(end)) {
          Address slot = buf.loadAddress();
          Word key = getKey(slot);
          if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(key.LE(prevKey));
          prevKey = key;
          buf = buf.plus(BYTES_IN_ADDRESS);
        }
        end = getPrev(end);
        buf = bufferStart(end);
      }
    }
  }
}
