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

import org.mmtk.vm.VM;

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

/**
 * This class implements basic memory copying, setting and clearing
 * operations.
 *
 * NOTE: Most of the operations in this class are performed at teh
 * granularity of a Java integer (ie 4-byte units)
 *
 * FIXME: Why can't these operations be performed at word-granularity?
 */
@Uninterruptible
public class Memory implements Constants {

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

  /** zero operations greater than this size are done using the
   * underlying OS implementation of zero() */
  private static final int SMALL_REGION_THRESHOLD = 1<<8; // empirically chosen


  /****************************************************************************
   *
   * Basic memory setting and zeroing operations
   */

  /**
   * Zero a region of memory
   *
   * @param start The start of the region to be zeroed (must be 4-byte aligned)
   * @param bytes The number of bytes to be zeroed (must be 4-byte aligned)
   */
  @Inline
  public static void zero(Address start, Extent bytes) {
    if (VM.VERIFY_ASSERTIONS) {
      assertAligned(start);
      assertAligned(bytes);
    }
    if (bytes.GT(Extent.fromIntZeroExtend(SMALL_REGION_THRESHOLD)))
      VM.memory.zero(start, bytes);
    else
      zeroSmall(start, bytes);
  }

  /**
   * Zero a small region of memory
   *
   * @param start The start of the region to be zeroed (must be 4-byte aligned)
   * @param bytes The number of bytes to be zeroed (must be 4-byte aligned)
   */
  @Inline
  public static void zeroSmall(Address start, Extent bytes) {
    if (VM.VERIFY_ASSERTIONS) {
      assertAligned(start);
      assertAligned(bytes);
    }
    Address end = start.plus(bytes);
    for (Address addr = start; addr.LT(end); addr = addr.plus(BYTES_IN_INT))
      addr.store(0);
  }

  /**
   * Set a region of memory
   *
   * @param start The start of the region to be zeroed (must be 4-byte aligned)
   * @param bytes The number of bytes to be zeroed (must be 4-byte aligned)
   * @param value The value to which the integers in the region should be set
   */
  @Inline
  public static void set(Address start, int bytes, int value) {
    if (VM.VERIFY_ASSERTIONS) {
      assertAligned(start);
      assertAligned(bytes);
    }
    Address end = start.plus(bytes);
    for (Address addr = start; addr.LT(end); addr = addr.plus(BYTES_IN_INT))
      addr.store(value);
  }


  /****************************************************************************
   *
   * Helper methods
   */

  /**
   * Check that a memory range is zeroed
   *
   * @param start The start address of the range to be checked
   * @param bytes The size of the region to be checked, in bytes
   * @return True if the region is zeroed
   */
  @Inline
  public static boolean IsZeroed(Address start, int bytes) {
    return isSet(start, bytes, false, 0);
  }

  /**
   * Assert that a memory range is zeroed.  An assertion failure will
   * occur if the region is not zeroed.
   *
   * this is in the inline allocation sequence when
   * VM.VERIFY_ASSERTIONS is true, it is carefully written to
   * reduce the impact on code space.
   *
   * @param start The start address of the range to be checked
   * @param bytes The size of the region to be checked, in bytes
   */
  @NoInline
  public static void assertIsZeroed(Address start, int bytes) {
    if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(isSet(start, bytes, true, 0));
  }

  /**
   * Verbosely check and return true if a memory range is set to some
   * integer value
   *
   * @param start The start address of the range to be checked
   * @param bytes The size of the region to be checked, in bytes
   * @param value The value to which this region should be set
   * @return True if the region has been correctly set
   */
  @Inline
  public static boolean isSet(Address start, int bytes, int value) {
    return isSet(start, bytes, true, value);
  }

  /**
   * Assert appropriate alignment, triggering an assertion failure if
   * the value does not satisify the alignment requirement of the
   * memory operations.
   *
   * @param value The value to be tested
   */
  private static void assertAligned(int value) {
    if (VM.VERIFY_ASSERTIONS) VM.assertions._assert((value & (BYTES_IN_INT - 1)) == 0);
  }

  private static void assertAligned(Word value) {
    if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(value.and(Word.fromIntSignExtend(BYTES_IN_INT-1)).isZero());
  }

  private static void assertAligned(Extent value) {
    assertAligned(value.toWord());
  }

  private static void assertAligned(Address value) {
    assertAligned(value.toWord());
  }

  /**
   * Test whether a memory range is set to a given integer value
   *
   * @param start The address to start checking at
   * @param bytes The size of the region to check, in bytes
   * @param verbose If true, produce verbose output
   * @param value The value to which the memory should be set
   */
  @NoInline
  private static boolean isSet(Address start, int bytes, boolean verbose,
      int value)
    /* Inlining this loop into the uninterruptible code can
     *  cause/encourage the GCP into moving a get_obj_tib into the
     * interruptible region where the tib is being installed via an
     * int_store
   */ {
    if (VM.VERIFY_ASSERTIONS) assertAligned(bytes);
    for (int i = 0; i < bytes; i += BYTES_IN_INT)
      if (start.loadInt(Offset.fromIntSignExtend(i)) != value) {
        if (verbose) {
          Log.prependThreadId();
          Log.write("VM range does not contain only value ");
          Log.writeln(value);
          Log.write("Non-zero range: "); Log.write(start);
          Log.write(" .. "); Log.writeln(start.plus(bytes));
          Log.write("First bad value at "); Log.writeln(start.plus(i));
          dumpMemory(start, 0, bytes);
        }
        return false;
      }
    return true;
  }

  /**
   * Dump the contents of memory around a given address
   *
   * @param addr The address around which the memory should be dumped
   * @param beforeBytes The number of bytes before the address to be
   * included in the dump
   * @param afterBytes The number of bytes after the address to be
   * included in the dump
   */
  public static void dumpMemory(Address addr, int beforeBytes, int afterBytes) {
    VM.memory.dumpMemory(addr, beforeBytes, afterBytes);
  }
}
