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

import org.mmtk.utility.*;

import org.mmtk.vm.Lock;
import org.mmtk.vm.VM;

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

/**
 * This class implements mmapping and protection of virtual memory.
 */
@Uninterruptible public final class Mmapper implements Constants {

  /****************************************************************************
   * Constants
   */
  public static final byte UNMAPPED = 0;
  public static final byte MAPPED = 1;
  public static final byte PROTECTED = 2; // mapped but not accessible
  public static final int LOG_MMAP_CHUNK_BYTES = 20;
  public static final int MMAP_CHUNK_BYTES = 1 << LOG_MMAP_CHUNK_BYTES;   // the granularity VMResource operates at
  //TODO: 64-bit: this is not OK: value does not fit in int, but should, we do not want to create such big array
  private static final int MMAP_CHUNK_MASK = MMAP_CHUNK_BYTES - 1;
  private static final int MMAP_NUM_CHUNKS = 1 << (Constants.LOG_BYTES_IN_ADDRESS_SPACE - LOG_MMAP_CHUNK_BYTES);
  public static final boolean verbose = false;

  /****************************************************************************
   * Class variables
   */
  public static final Lock lock = VM.newLock("Mmapper");
  private static byte[] mapped;


  /****************************************************************************
   * Initialization
   */

  /**
   * Class initializer.  This is executed <i>prior</i> to bootstrap
   * (i.e. at "build" time).
   */
  static {
    mapped = new byte[MMAP_NUM_CHUNKS];
    for (int c = 0; c < MMAP_NUM_CHUNKS; c++) {
      mapped[c] = UNMAPPED;
    }
  }

  /****************************************************************************
   * Generic mmap and protection functionality
   */

  /**
   * Given an address array describing the regions of virtual memory to be used
   * by MMTk, demand zero map all of them if they are not already mapped.
   *
   * @param spaceMap An address array containing a pairs of start and end
   * addresses for each of the regions to be mappe3d
   */
  public static void eagerlyMmapAllSpaces(AddressArray spaceMap) {

    /*for (int i = 0; i < spaceMap.length() / 2; i++) {
      Address regionStart = spaceMap.get(i * 2);
      Address regionEnd = spaceMap.get(i * 2 + 1);
      Log.write(regionStart); Log.write(" "); Log.writeln(regionEnd);
      if (regionEnd.EQ(Address.zero()) || regionStart.EQ(Address.fromIntSignExtend(-1)) ||regionEnd.EQ(Address.fromIntSignExtend(-1)))
          break;
      if (VM.VERIFY_ASSERTIONS) {
        VM.assertions._assert(regionStart.EQ(chunkAlignDown(regionStart)));
        VM.assertions._assert(regionEnd.EQ(chunkAlignDown(regionEnd)));
      }
      int pages = Conversions.bytesToPages(regionEnd.diff(regionStart));
      ensureMapped(regionStart, pages);
    }*/
  }

  /**
   *  Mark a range of pages as having (already) been mapped.  This is useful
   *  where the VM has performed the mapping of the pages itself.
   *
   * @param start The start of the range to be marked as mapped
   * @param bytes The size of the range, in bytes.
   */
  public static void markAsMapped(Address start, int bytes) {
    int startChunk = Conversions.addressToMmapChunksDown(start);
    int endChunk = Conversions.addressToMmapChunksUp(start.plus(bytes));
    for (int i = startChunk; i <= endChunk; i++)
      mapped[i] = MAPPED;
  }

  /**
   * Ensure that a range of pages is mmapped (or equivalent).  If the
   * pages are not yet mapped, demand-zero map them. Note that mapping
   * occurs at chunk granularity, not page granularity.<p>
   *
   * NOTE: There is a monotonicity assumption so that only updates require lock
   * acquisition.
   * TODO: Fix the above to support unmapping.
   *
   * @param start The start of the range to be mapped.
   * @param pages The size of the range to be mapped, in pages
   */
  public static void ensureMapped(Address start, int pages) {
    int startChunk = Conversions.addressToMmapChunksDown(start);
    int endChunk = Conversions.addressToMmapChunksUp(start.plus(Conversions.pagesToBytes(pages)));
    for (int chunk = startChunk; chunk < endChunk; chunk++) {
      if (mapped[chunk] == MAPPED) continue;
      Address mmapStart = Conversions.mmapChunksToAddress(chunk);
      lock.acquire();
//      Log.writeln(mmapStart);
      // might have become MAPPED here
      if (mapped[chunk] == UNMAPPED) {
        int errno = VM.memory.dzmmap(mmapStart, MMAP_CHUNK_BYTES);
        if (errno != 0) {
          lock.release();
          Log.write("ensureMapped failed with errno "); Log.write(errno);
          Log.write(" on address "); Log.writeln(mmapStart);
          if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(false);
        } else {
          if (verbose) {
            Log.write("mmap succeeded at chunk "); Log.write(chunk);  Log.write("  "); Log.write(mmapStart);
            Log.write(" with len = "); Log.writeln(MMAP_CHUNK_BYTES);
          }
        }
      }
      if (mapped[chunk] == PROTECTED) {
        if (!VM.memory.munprotect(mmapStart, MMAP_CHUNK_BYTES)) {
          lock.release();
          VM.assertions.fail("Mmapper.ensureMapped (unprotect) failed");
        } else {
          if (verbose) {
            Log.write("munprotect succeeded at chunk "); Log.write(chunk);  Log.write("  "); Log.write(mmapStart);
            Log.write(" with len = "); Log.writeln(MMAP_CHUNK_BYTES);
          }
        }
      }
      mapped[chunk] = MAPPED;
      lock.release();
    }

  }

  /**
   * Memory protect a range of pages (using mprotect or equivalent).  Note
   * that protection occurs at chunk granularity, not page granularity.
   *
   * @param start The start of the range to be protected.
   * @param pages The size of the range to be protected, in pages
   */
  public static void protect(Address start, int pages) {
    int startChunk = Conversions.addressToMmapChunksDown(start);
    int chunks = Conversions.pagesToMmapChunksUp(pages);
    int endChunk = startChunk + chunks;
    lock.acquire();
    for (int chunk = startChunk; chunk < endChunk; chunk++) {
      if (mapped[chunk] == MAPPED) {
        Address mmapStart = Conversions.mmapChunksToAddress(chunk);
        if (!VM.memory.mprotect(mmapStart, MMAP_CHUNK_BYTES)) {
          lock.release();
          VM.assertions.fail("Mmapper.mprotect failed");
        } else {
          if (verbose) {
            Log.write("mprotect succeeded at chunk "); Log.write(chunk);  Log.write("  "); Log.write(mmapStart);
            Log.write(" with len = "); Log.writeln(MMAP_CHUNK_BYTES);
          }
        }
        mapped[chunk] = PROTECTED;
      } else {
        if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(mapped[chunk] == PROTECTED);
      }
    }
    lock.release();
  }

  /****************************************************************************
   * Utility functions
   */

  /**
   * Return true if the given address has been mmapped
   *
   * @param addr The address in question.
   * @return true if the given address has been mmapped
   */
  @Uninterruptible
  public static boolean addressIsMapped(Address addr) {
    int chunk = Conversions.addressToMmapChunksDown(addr);
    return mapped[chunk] == MAPPED;
  }

  /**
   * Return true if the given object has been mmapped
   *
   * @param object The object in question.
   * @return true if the given object has been mmapped
   */
  @Uninterruptible
  public static boolean objectIsMapped(ObjectReference object) {
    return addressIsMapped(VM.objectModel.refToAddress(object));
  }

  /**
   * Return a given address rounded up to an mmap chunk size
   *
   * @param addr The address to be aligned
   * @return The given address rounded up to an mmap chunk size
   */
  @SuppressWarnings("unused")  // but might be useful someday
  private static Address chunkAlignUp(Address addr) {
    return chunkAlignDown(addr.plus(MMAP_CHUNK_MASK));
  }

  /**
   * Return a given address rounded down to an mmap chunk size
   *
   * @param addr The address to be aligned
   * @return The given address rounded down to an mmap chunk size
   */
  private static Address chunkAlignDown(Address addr) {
    return addr.toWord().and(Word.fromIntSignExtend(MMAP_CHUNK_MASK).not()).toAddress();
  }
}

