//===- Space.cpp ----------------------------------------------------------===//
//
//                     The MCLinker Project
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include <mcld/Support/Space.h>
#include <mcld/Support/FileHandle.h>
#include <mcld/Support/MsgHandling.h>
#include <cstdlib>
#include <unistd.h>

using namespace mcld;

//===----------------------------------------------------------------------===//
// constant data
static const off_t PageSize = getpagesize();

//===----------------------------------------------------------------------===//
// Non-member functions
//
// low address      A page             high address
// |--------------------|------------------|
// ^ page_offset        ^ pFileOffset      ^ page_boundary

// Given a file offset, return the page offset.
// return the first page boundary \b before pFileOffset
inline static off_t page_offset(off_t pFileOffset)
{ return pFileOffset & ~ (PageSize - 1); }

// page_boundary - Given a file size, return the size to read integral pages.
// return the first page boundary \b after pFileOffset
inline static off_t page_boundary(off_t pFileOffset)
{ return (pFileOffset + (PageSize - 1)) & ~ (PageSize - 1); }

inline static Space::Type policy(off_t pOffset, size_t pLength)
{
  const size_t threshold = (PageSize*3)/4; // 3/4 page size in Linux
  if (pLength < threshold)
    return Space::ALLOCATED_ARRAY;
  else
    return Space::MMAPED;
}

//===----------------------------------------------------------------------===//
// Space
Space::Space()
  : m_Data(NULL), m_StartOffset(0), m_Size(0),
    m_RegionCount(0), m_Type(UNALLOCATED) {
}

Space::Space(Space::Type pType, void* pMemBuffer, size_t pSize)
  : m_Data(static_cast<Address>(pMemBuffer)), m_StartOffset(0), m_Size(pSize),
    m_RegionCount(0), m_Type(pType)
{
}

Space::~Space()
{
  // do nothing. m_Data is deleted by @ref releaseSpace
}

Space* Space::createSpace(FileHandle& pHandler,
                          size_t pStart, size_t pSize)
{
  Type type;
  void* memory;
  Space* result = NULL;
  size_t start, size = 0, total_offset;
  switch(type = policy(pStart, pSize)) {
    case ALLOCATED_ARRAY: {
      // adjust total_offset, start and size
      total_offset = pStart + pSize;
      start = pStart;
      if (total_offset > pHandler.size()) {
        if (pHandler.isWritable()) {
          size = pSize;
          pHandler.truncate(total_offset);
        }
        else if (pHandler.size() > start)
          size = pHandler.size() - start;
        else {
          // create a space out of a read-only file.
          fatal(diag::err_cannot_read_small_file) << pHandler.path()
                                                  << pHandler.size()
                                                  << start << size;
        }
      }
      else
        size = pSize;

      // malloc
      memory = (void*)malloc(size);
      if (!pHandler.read(memory, start, size))
        error(diag::err_cannot_read_file) << pHandler.path() << start << size;

      break;
    }
    case MMAPED: {
      // adjust total_offset, start and size
      total_offset = page_boundary(pStart + pSize);
      start = page_offset(pStart);
      if (total_offset > pHandler.size()) {
        if (pHandler.isWritable()) {
          size = page_boundary((pStart - start) + pSize);
          pHandler.truncate(total_offset);
        }
        else if (pHandler.size() > start)
          size = pHandler.size() - start;
        else {
          // create a space out of a read-only file.
          fatal(diag::err_cannot_read_small_file) << pHandler.path()
                                                  << pHandler.size()
                                                  << start << size;
        }
      }
      else
        size = page_boundary((pStart - start) + pSize);

      // mmap
      if (!pHandler.mmap(memory, start, size))
        error(diag::err_cannot_mmap_file) << pHandler.path() << start << size;

      break;
    }
    default:
      break;
  } // end of switch

  result = new Space(type, memory, size);
  result->setStart(start);
  return result;
}

void Space::releaseSpace(Space* pSpace, FileHandle& pHandler)
{
  if (NULL == pSpace)
    return;

  switch(pSpace->type()) {
    case ALLOCATED_ARRAY:
      free(pSpace->memory());
      break;
    case MMAPED:
      if (!pHandler.munmap(pSpace->memory(), pSpace->size()))
        error(diag::err_cannot_munmap_file) << pHandler.path();
      break;
    default: // external and unallocated memory buffers
      break;
  } // end of switch
}

void Space::syncSpace(Space* pSpace, FileHandle& pHandler)
{
  if (NULL == pSpace || !pHandler.isWritable())
    return;

  switch(pSpace->type()) {
    case Space::ALLOCATED_ARRAY: {
      if (!pHandler.write(pSpace->memory(),
                          pSpace->start(),
                          pSpace->size())) {
        error(diag::err_cannot_write_file) << pHandler.path()
                                           << pSpace->start()
                                           << pSpace->size();
      }
      return;
    }
    case Space::MMAPED:
    default: {
      // system will eventually write bakc the memory after
      // calling ::munmap
      return;
    }
  } // end of switch
}