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

import org.mmtk.policy.Space;
import org.mmtk.policy.immix.Block;
import org.mmtk.policy.immix.Chunk;
import org.mmtk.policy.immix.Line;
import org.mmtk.policy.immix.ImmixSpace;
import static org.mmtk.policy.immix.ImmixConstants.*;

import org.mmtk.utility.Constants;
import org.mmtk.utility.Log;
import org.mmtk.vm.VM;

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

/**
 *
 */
@Uninterruptible
public class ImmixAllocator extends Allocator implements Constants {

  /****************************************************************************
   *
   * Instance variables
   */
  protected final ImmixSpace space;    /* space this allocator is associated with */
  private final boolean hot;
  private final boolean copy;

  private Address cursor;               /* bump pointer */
  private Address limit;                /* limit for bump pointer */
  private Address largeCursor;          /* bump pointer for large objects */
  private Address largeLimit;           /* limit for bump pointer for large objects */
  private boolean requestForLarge;      /* is the current request for large or small? */
  private boolean straddle;             /* did the last allocation straddle a line? */
  private int lineUseCount;             /* approximation to bytes allocated (measured at 99% accurate)  07/10/30 */
  private Address markTable;
  private Address recyclableBlock;
  private int line;
  private boolean recyclableExhausted;

  /**
   * Constructor.
   *
   * @param space The space to bump point into.
   * @param hot TODO
   * @param copy TODO
   */
  public ImmixAllocator(ImmixSpace space, boolean hot, boolean copy) {
    this.space = space;
    this.hot = hot;
    this.copy = copy;
    reset();
  }

  /**
   * Reset the allocator. Note that this does not reset the space.
   */
  public void reset() {
    cursor = Address.zero();
    limit = Address.zero();
    largeCursor = Address.zero();
    largeLimit = Address.zero();
    markTable = Address.zero();
    recyclableBlock = Address.zero();
    requestForLarge = false;
    recyclableExhausted = false;
    line = LINES_IN_BLOCK;
    lineUseCount = 0;
  }

  /*****************************************************************************
   *
   * Public interface
   */

  /**
   * Allocate space for a new object.  This is frequently executed code and
   * the coding is deliberaetly sensitive to the optimizing compiler.
   * After changing this, always check the IR/MC that is generated.
   *
   * @param bytes The number of bytes allocated
   * @param align The requested alignment
   * @param offset The offset from the alignment
   * @return The address of the first byte of the allocated region
   */
  @Inline
  public final Address alloc(int bytes, int align, int offset) {
    /* establish how much we need */
    Address start = alignAllocationNoFill(cursor, align, offset);
    Address end = start.plus(bytes);

    /* check whether we've exceeded the limit */
    if (end.GT(limit)) {
      if (bytes > BYTES_IN_LINE)
        return overflowAlloc(bytes, align, offset);
      else
        return allocSlowHot(bytes, align, offset);
    }

    /* sufficient memory is available, so we can finish performing the allocation */
    fillAlignmentGap(cursor, start);
    cursor = end;

    return start;
  }

  /**
   * Allocate space for a new object.  This is frequently executed code and
   * the coding is deliberaetly sensitive to the optimizing compiler.
   * After changing this, always check the IR/MC that is generated.
   *
   * @param bytes The number of bytes allocated
   * @param align The requested alignment
   * @param offset The offset from the alignment
   * @return The address of the first byte of the allocated region
   */
  public final Address overflowAlloc(int bytes, int align, int offset) {
    /* establish how much we need */
    Address start = alignAllocationNoFill(largeCursor, align, offset);
    Address end = start.plus(bytes);

    /* check whether we've exceeded the limit */
    if (end.GT(largeLimit)) {
      requestForLarge = true;
      Address rtn =  allocSlowInline(bytes, align, offset);
      requestForLarge = false;
      return rtn;
    }

    /* sufficient memory is available, so we can finish performing the allocation */
    fillAlignmentGap(largeCursor, start);
    largeCursor = end;

    return start;
  }

  @Inline
  public final boolean getLastAllocLineStraddle() {
    return straddle;
  }

  /**
   * External allocation slow path (called by superclass when slow path is
   * actually taken.  This is necessary (rather than a direct call
   * from the fast path) because of the possibility of a thread switch
   * and corresponding re-association of bump pointers to kernel
   * threads.
   *
   * @param bytes The number of bytes allocated
   * @param align The requested alignment
   * @param offset The offset from the alignment
   * @return The address of the first byte of the allocated region or
   * zero on failure
   */
  protected final Address allocSlowOnce(int bytes, int align, int offset) {
    boolean success = false;
    while (!success) {
      Address ptr = space.getSpace(hot, copy, lineUseCount);

      if (ptr.isZero()) {
        lineUseCount = 0;
        return ptr; // failed allocation --- we will need to GC
      }

      /* we have been given a clean block */
      success = true;
      lineUseCount = LINES_IN_BLOCK;
      if (VM.VERIFY_ASSERTIONS)
        VM.assertions._assert(Block.isAligned(ptr));
      zeroBlock(ptr);
      if (requestForLarge) {
        largeCursor = ptr;
        largeLimit = ptr.plus(BYTES_IN_BLOCK);
      } else {
        cursor = ptr;
        limit = ptr.plus(BYTES_IN_BLOCK);
      }
    }
    return alloc(bytes, align, offset);
  }

  /****************************************************************************
   *
   * Bump allocation
   */

  /**
   * Internal allocation slow path.  This is called whenever the bump
   * pointer reaches the internal limit.  The code is forced out of
   * line.  If required we perform an external slow path take, which
   * we inline into this method since this is already out of line.
   *
   * @param bytes The number of bytes allocated
   * @param align The requested alignment
   * @param offset The offset from the alignment
   * @return The address of the first byte of the allocated region
   */
  @NoInline
  private Address allocSlowHot(int bytes, int align, int offset) {
    if (acquireRecyclableLines(bytes, align, offset))
      return alloc(bytes, align, offset);
    else
      return allocSlowInline(bytes, align, offset);
  }

  private boolean acquireRecyclableLines(int bytes, int align, int offset) {
    while (line < LINES_IN_BLOCK || acquireRecyclableBlock()) {
      line = Line.getNextUnused(markTable, line);
      if (line < LINES_IN_BLOCK) {
        int endLine = Line.getNextUsed(markTable, line);
        cursor = recyclableBlock.plus(Extent.fromIntSignExtend(line<<LOG_BYTES_IN_LINE));
        limit = recyclableBlock.plus(Extent.fromIntSignExtend(endLine<<LOG_BYTES_IN_LINE));
        if (SANITY_CHECK_LINE_MARKS) {
          Address tmp = cursor;
          while (tmp.LT(limit)) {
            if (tmp.loadByte() != (byte) 0) {
              Log.write("cursor: "); Log.writeln(cursor);
              Log.write(" limit: "); Log.writeln(limit);
              Log.write("current: "); Log.write(tmp);
              Log.write("  value: "); Log.write(tmp.loadByte());
              Log.write("   line: "); Log.write(line);
              Log.write("endline: "); Log.write(endLine);
              Log.write("  chunk: "); Log.write(Chunk.align(cursor));
              Log.write("     hw: "); Log.write(Chunk.getHighWater(Chunk.align(cursor)));
              Log.writeln(" values: ");
              Address tmp2 = cursor;
              while(tmp2.LT(limit)) { Log.write(tmp2.loadByte()); Log.write(" ");}
              Log.writeln();
            }
            VM.assertions._assert(tmp.loadByte() == (byte) 0);
            tmp = tmp.plus(1);
          }
        }
        if (VM.VERIFY_ASSERTIONS && bytes <= BYTES_IN_LINE) {
          Address start = alignAllocationNoFill(cursor, align, offset);
          Address end = start.plus(bytes);
          VM.assertions._assert(end.LE(limit));
        }
        VM.memory.zero(cursor, limit.diff(cursor).toWord().toExtent());
        line = endLine;
        if (VM.VERIFY_ASSERTIONS && copy) VM.assertions._assert(!Block.isDefragSource(cursor));
        return true;
      }
    }
    return false;
  }

  private boolean acquireRecyclableBlock() {
    boolean rtn;
    rtn = acquireRecyclableBlockAddressOrder();
    if (rtn) {
      markTable = Line.getBlockMarkTable(recyclableBlock);
      line = 0;
    }
    return rtn;
  }

  @Inline
  private boolean acquireRecyclableBlockAddressOrder() {
    if (recyclableExhausted) return false;
    int markState = 0;
    boolean usable = false;
    while (!usable) {
      Address next = recyclableBlock.plus(BYTES_IN_BLOCK);
      if (recyclableBlock.isZero() || ImmixSpace.isRecycleAllocChunkAligned(next)) {
        recyclableBlock = space.acquireReusableBlocks();
        if (recyclableBlock.isZero()) {
          recyclableExhausted = true;
          line = LINES_IN_BLOCK;
          return false;
        }
      } else {
        recyclableBlock = next;
      }
      markState = Block.getBlockMarkState(recyclableBlock);
      usable = (markState > 0 && markState <= ImmixSpace.getReusuableMarkStateThreshold(copy));
      if (copy && Block.isDefragSource(recyclableBlock))
        usable = false;
    }
    if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(!Block.isUnused(recyclableBlock));
    Block.setBlockAsReused(recyclableBlock);

    lineUseCount += (LINES_IN_BLOCK-markState);
    return true; // found something good
  }

  private void zeroBlock(Address block) {
    // FIXME: efficiency check here!
    if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(block.toWord().and(Word.fromIntSignExtend(BYTES_IN_BLOCK-1)).isZero());
    VM.memory.zeroPages(block, BYTES_IN_BLOCK);
   }

  /** @return the space associated with this squish allocator */
  public final Space getSpace() { return space; }

  /**
   * Print out the status of the allocator (for debugging)
   */
  public final void show() {
    Log.write("cursor = "); Log.write(cursor);
    Log.write(" limit = "); Log.writeln(limit);
  }
}
