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

import org.mmtk.plan.markcompact.MC;
import org.mmtk.utility.Conversions;
import org.mmtk.utility.alloc.Allocator;
import org.mmtk.utility.alloc.BumpPointer;
import org.mmtk.vm.VM;

import org.vmmagic.pragma.*;
import org.vmmagic.unboxed.Address;
import org.vmmagic.unboxed.Extent;
import org.vmmagic.unboxed.ObjectReference;

/**
 * This class implements unsynchronized (local) elements of a
 * sliding mark-compact collector. Allocation is via the bump pointer
 * (@see BumpPointer).
 *
 * @see BumpPointer
 * @see MarkCompactSpace
 */
@Uninterruptible public final class MarkCompactLocal extends BumpPointer {

  /**
   * Constructor
   *
   * @param space The space to bump point into.
   */
  public MarkCompactLocal(MarkCompactSpace space) {
    super(space, true);
  }

  /**
   * Perform the compacting phase of the collection.
   */
  public void compact() {
    /* Has this allocator ever allocated anything? */
    if (initialRegion.isZero()) return;

    /* Loop through active regions or until the last region */
    Address start = initialRegion;
    Address allocStart = initialRegion;
    Address allocEnd = initialRegion.plus(REGION_LIMIT_OFFSET).loadAddress();
    Address allocCursor = allocStart.plus(DATA_START_OFFSET);

    /* Keep track of which regions are being used */
    int oldPages = 0;
    int newPages = Conversions.bytesToPages(allocEnd.diff(allocStart).plus(BYTES_IN_ADDRESS));

    while (!start.isZero()) {
      /* Get the end of this region */
      Address end = start.plus(REGION_LIMIT_OFFSET).loadAddress();
      Address dataEnd = start.plus(DATA_END_OFFSET).loadAddress();
      Address nextRegion = start.plus(NEXT_REGION_OFFSET).loadAddress();
      oldPages += Conversions.bytesToPages(end.diff(start).plus(BYTES_IN_ADDRESS));

      /* dataEnd = zero represents the current region. */
      Address currentLimit = (dataEnd.isZero() ? cursor : dataEnd);
      ObjectReference current =
        VM.objectModel.getObjectFromStartAddress(start.plus(DATA_START_OFFSET));

      while (VM.objectModel.refToAddress(current).LT(currentLimit) && !current.isNull()) {
        ObjectReference next = VM.objectModel.getNextObject(current);

        ObjectReference copyTo = MarkCompactSpace.getForwardingPointer(current);

        if (!copyTo.isNull() && Space.isInSpace(MC.MARK_COMPACT, copyTo)) {
          if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(!MarkCompactSpace.isMarked(current));
          // To be copied.
          if (copyTo.toAddress().GT(allocEnd) || copyTo.toAddress().LT(allocStart)) {
            // changed regions.

            VM.memory.zero(allocCursor, allocEnd.diff(allocCursor).toWord().toExtent().plus(BYTES_IN_ADDRESS));

            allocStart.store(allocCursor, DATA_END_OFFSET);
            allocStart = allocStart.plus(NEXT_REGION_OFFSET).loadAddress();
            allocEnd = allocStart.plus(REGION_LIMIT_OFFSET).loadAddress();
            allocCursor = allocStart.plus(DATA_START_OFFSET);

            newPages += Conversions.bytesToPages(allocEnd.diff(allocStart).plus(BYTES_IN_ADDRESS));

            if (VM.VERIFY_ASSERTIONS) {
              VM.assertions._assert(allocCursor.LT(allocEnd) && allocCursor.GE(allocStart));
            }
          }
          allocCursor = VM.objectModel.copyTo(current, copyTo, allocCursor);
          MarkCompactSpace.setForwardingPointer(copyTo, ObjectReference.nullReference());
        }
        current = next;
      }
      if (dataEnd.isZero()) {
        break;
      }
      start = nextRegion;
    }
    Extent zeroBytes = allocEnd.diff(allocCursor).toWord().toExtent().plus(BYTES_IN_ADDRESS);
    VM.memory.zero(allocCursor, zeroBytes);

    allocStart.store(Address.zero(), DATA_END_OFFSET);
    region = allocStart;
    cursor = allocCursor;
    updateLimit(allocEnd, region, 0);
    if (oldPages > newPages) {
      ((MarkCompactSpace) space).unusePages((oldPages - newPages));
    }

    // Zero during GC to help debugging.
    allocStart = allocStart.loadAddress(NEXT_REGION_OFFSET);
    while (!allocStart.isZero()) {
      allocStart.store(Address.zero(), DATA_END_OFFSET);
      if (VM.VERIFY_ASSERTIONS) {
        Address low = allocStart.plus(DATA_START_OFFSET);
        Extent size = allocStart.loadAddress(REGION_LIMIT_OFFSET).diff(allocStart).toWord().toExtent().minus(2 * BYTES_IN_ADDRESS);
        VM.memory.zero(low, size);
      }
      allocStart = allocStart.loadAddress(NEXT_REGION_OFFSET);
    }
  }

  /**
   * Perform a linear scan through the objects allocated by this bump pointer,
   * calculating where each live object will be post collection.
   */
  public void calculateForwardingPointers() {
    /* Has this allocator ever allocated anything? */
    if (initialRegion.isZero()) return;

    /* Loop through active regions or until the last region */
    Address start = initialRegion;
    Address allocStart = initialRegion;
    Address allocDataEnd = initialRegion.plus(DATA_END_OFFSET).loadAddress();
    Address allocLimit = (allocDataEnd.isZero() ? cursor : allocDataEnd);
    Address allocCursor = start.plus(DATA_START_OFFSET);

    while (!start.isZero()) {
      /* Get the end of this region */
      Address dataEnd = start.plus(DATA_END_OFFSET).loadAddress();

      /* dataEnd = zero represents the current region. */
      Address currentLimit = (dataEnd.isZero() ? cursor : dataEnd);
      ObjectReference current =
        VM.objectModel.getObjectFromStartAddress(start.plus(DATA_START_OFFSET));

      while (VM.objectModel.refToAddress(current).LT(currentLimit) && !current.isNull()) {
        ObjectReference next = VM.objectModel.getNextObject(current);

        if (MarkCompactSpace.toBeCompacted(current)) {
          if (VM.VERIFY_ASSERTIONS)
            VM.assertions._assert(MarkCompactSpace.getForwardingPointer(current).isNull());

          // Fake - allocate it.
          int size = VM.objectModel.getSizeWhenCopied(current);
          int align = VM.objectModel.getAlignWhenCopied(current);
          int offset = VM.objectModel.getAlignOffsetWhenCopied(current);
          allocCursor = Allocator.alignAllocationNoFill(allocCursor, align, offset);

          boolean sameRegion = allocStart.EQ(start);

          if (!sameRegion && allocCursor.plus(size).GT(allocLimit)) {
            allocStart = allocStart.plus(NEXT_REGION_OFFSET).loadAddress();
            allocDataEnd = allocStart.plus(DATA_END_OFFSET).loadAddress();
            allocLimit = (allocDataEnd.isZero() ? cursor : allocDataEnd);
            allocCursor = Allocator.alignAllocationNoFill(allocStart.plus(DATA_START_OFFSET), align, offset);
          }

          ObjectReference target = VM.objectModel.getReferenceWhenCopiedTo(current, allocCursor);
          if (sameRegion && target.toAddress().GE(current.toAddress())) {
            MarkCompactSpace.setForwardingPointer(current, current);
            allocCursor = VM.objectModel.getObjectEndAddress(current);
          } else {
            MarkCompactSpace.setForwardingPointer(current, target);
            allocCursor = allocCursor.plus(size);
          }
        }
        current = next;
      }
      if (dataEnd.isZero()) {
        break;
      }
      start = start.plus(NEXT_REGION_OFFSET).loadAddress(); // Move on to next
    }
  }

  /**
   * Some pages are about to be re-used to satisfy a slow path request.
   * @param pages The number of pages.
   */
  protected void reusePages(int pages) {
    ((MarkCompactSpace)space).reusePages(pages);
  }

  /**
   * Maximum size of a single region. Important for children that implement
   * load balancing or increments based on region size.
   * @return the maximum region size
   */
  protected Extent maximumRegionSize() { return Extent.fromIntZeroExtend(4 << LOG_CHUNK_SIZE) ; }
}
