/* nailboard.c: NAILBOARD IMPLEMENTATION
 *
 * $Id$
 * Copyright (c) 2014-2020 Ravenbrook Limited.  See end of file for license.
 *
 * .sources: <design/nailboard>.
 */

#include "bt.h"
#include "check.h"
#include "mpm.h"
#include "nailboard.h"

SRCID(nailboard, "$Id$");


/* Log2 of scale factor between levels. <design/nailboard#.impl.scale>. */
#define LEVEL_SHIFT MPS_WORD_SHIFT


/* nailboardLevels -- return the number of levels in a nailboard with
 * the given number of nails.
 *
 * <design/nailboard#.impl.table.last>
 */

static Count nailboardLevels(Count nails)
{
  return (SizeFloorLog2((Size)nails) + LEVEL_SHIFT) / LEVEL_SHIFT;
}


/* nailboardNails -- return the total number of nails in the board */

static Count nailboardNails(Nailboard board)
{
  return RangeSize(&board->range) >> board->alignShift;
}


/* nailboardLevelBits -- return the number of bits in the bit table
 * for the given level.
 */

static Count nailboardLevelBits(Count nails, Index level)
{
  Shift shift = (Shift)(level * LEVEL_SHIFT);
  return (nails + ((Count)1 << shift) - 1) >> shift;
}

Bool NailboardCheck(Nailboard board)
{
  Index i;
  Count nails;
  CHECKS(Nailboard, board);
  CHECKL(RangeCheck(&board->range));
  CHECKL(0 < board->levels);
  nails = nailboardNails(board);
  CHECKL(board->levels == nailboardLevels(nails));
  CHECKL(nails == nailboardLevelBits(nails, 0));
  CHECKL(nailboardLevelBits(nails, board->levels - 1) != 0);
  CHECKL(nailboardLevelBits(nails, board->levels) == 1);
  CHECKL(BoolCheck(board->newNails));
  for (i = 0; i < board->levels; ++i) {
    CHECKL(board->level[i] != NULL);
  }
  return TRUE;
}


/* nailboardStructSize -- return the size of the nailboard structure,
 * plus the array of pointers to levels.
 */

static Size nailboardStructSize(Count levels)
{
  return offsetof(NailboardStruct, level) + sizeof(BT *) * levels;
}


/* nailboardSize -- return the total size of the nailboard
 *
 * This is the size of the nailboard structure plus the combined sizes
 * of the bit tables.
 */

static Size nailboardSize(Count nails, Count levels)
{
  Index i;
  Size size;
  size = nailboardStructSize(levels);
  for (i = 0; i < levels; ++i) {
    size += BTSize(nailboardLevelBits(nails, i));
  }
  return size;
}


/* NailboardCreate -- allocate a nailboard
 *
 * Allocate a nailboard in the control pool for arena, to cover the
 * range of addresses from base to limit (which must be non-empty). If
 * successful, set *boardReturn to point to the nailboard and return
 * ResOK. Otherwise, return a result code to indicate failure.
 *
 * alignment specifies the granularity of the nails: that is, the
 * number of bytes covered by each nail.
 */

Res NailboardCreate(Nailboard *boardReturn, Arena arena, Align alignment,
                    Addr base, Addr limit)
{
  void *p;
  Nailboard board;
  Shift alignShift;
  Count nails, levels;
  Index i;
  Res res;

  AVER(boardReturn != NULL);
  AVERT(Arena, arena);
  AVERT(Align, alignment);
  AVER(base < limit);
  AVER(AddrIsAligned(base, alignment));
  AVER(AddrIsAligned(limit, alignment));

  alignShift = SizeLog2((Size)alignment);
  nails = AddrOffset(base, limit) >> alignShift;
  levels = nailboardLevels(nails);
  res = ControlAlloc(&p, arena, nailboardSize(nails, levels));
  if (res != ResOK)
    return res;

  board = p;
  RangeInit(&board->range, base, limit);
  board->levels = levels;
  board->alignShift = alignShift;
  board->newNails = FALSE;

  p = PointerAdd(p, nailboardStructSize(levels));
  for (i = 0; i < levels; ++i) {
    Count levelBits = nailboardLevelBits(nails, i);
    AVER(levelBits > 0);
    board->level[i] = p;
    BTResRange(board->level[i], 0, levelBits);
    p = PointerAdd(p, BTSize(levelBits));
  }

  board->sig = NailboardSig;
  AVERT(Nailboard, board);
  *boardReturn = board;
  return ResOK;
}


/* NailboardDestroy -- destroy a nailboard */

void NailboardDestroy(Nailboard board, Arena arena)
{
  Count nails;
  Size size;

  AVERT(Nailboard, board);
  AVERT(Arena, arena);

  nails = nailboardNails(board);
  size = nailboardSize(nails, board->levels);

  board->sig = SigInvalid;
  ControlFree(arena, board, size);
}


/* NailboardClearNewNails -- clear the "new nails" flag */

void (NailboardClearNewNails)(Nailboard board)
{
  AVERT(Nailboard, board);
  NailboardClearNewNails(board);
}


/* NailboardNewNails -- return the "new nails" flag
 *
 * Return TRUE if any new nails have been set in the nailboard since
 * the last call to NailboardClearNewNails (or since the nailboard was
 * created, if there have never been any such calls), FALSE otherwise.
 */

Bool (NailboardNewNails)(Nailboard board)
{
  AVERT(Nailboard, board);
  return NailboardNewNails(board);
}


/* nailboardIndex -- return the index of the nail corresponding to
 * addr in the given level.
 */

static Index nailboardIndex(Nailboard board, Index level, Addr addr)
{
  Index i = AddrOffset(RangeBase(&board->range), addr)
    >> (board->alignShift + level * LEVEL_SHIFT);
  AVER_CRITICAL(i < nailboardLevelBits(nailboardNails(board), level));
  return i;
}


/* nailboardAddr -- return the address corresponding to the index in
 * the given level.
 */

static Addr nailboardAddr(Nailboard board, Index level, Index index)
{
  return AddrAdd(RangeBase(&board->range),
                 index << (board->alignShift + level * LEVEL_SHIFT));
}


/* nailboardIndexRange -- update *ibaseReturn and *ilimitReturn to be
 * the interval of indexes of nails in the given level, corresponding
 * to the interval of addresses base and limit. See
 * <design/nailboard/#.impl.isresrange.alignment>.
 */

static void nailboardIndexRange(Index *ibaseReturn, Index *ilimitReturn,
                                Nailboard board, Index level,
                                Addr base, Addr limit)
{
  *ibaseReturn = nailboardIndex(board, level, base);
  *ilimitReturn = nailboardIndex(board, level, AddrSub(limit, 1)) + 1;
}


/* NailboardGet -- return nail corresponding to address
 *
 * Return the nail in the nailboard corresponding to the address addr.
 * It is an error if addr does not lie in the range of addresses
 * covered by the nailboard.
 */

Bool NailboardGet(Nailboard board, Addr addr)
{
  AVERT(Nailboard, board);
  AVER(RangeContains(&board->range, addr));
  return BTGet(board->level[0], nailboardIndex(board, 0, addr));
}


/* NailboardSet -- set nail corresponding to address
 *
 * Set the nail in the nailboard corresponding to the address addr.
 * Return the old nail at that position. It is an error if addr does
 * not lie in the range of addresses covered by the nailboard.
 *
 * This function is on the critical path because it is called for
 * every fix of an ambiguous reference to an address in an AMC pool.
 */

Bool NailboardSet(Nailboard board, Addr addr)
{
  Index i, j;

  AVERT_CRITICAL(Nailboard, board);
  AVER_CRITICAL(RangeContains(&board->range, addr));

  j = nailboardIndex(board, 0, addr);
  if (BTGet(board->level[0], j))
    return TRUE;
  board->newNails = TRUE;
  BTSet(board->level[0], j);

  for (i = 1; i < board->levels; ++i) {
    j = nailboardIndex(board, i, addr);
    if (BTGet(board->level[i], j))
      break;
    BTSet(board->level[i], j);
  }
  return FALSE;
}


/* NailboardSetRange -- set all nails in range
 *
 * Set all nails in the nailboard corresponding to the range between
 * base and limit. It is an error if any part of the range is not
 * covered by the nailboard, or if any nail in the range is set.
 */

void NailboardSetRange(Nailboard board, Addr base, Addr limit)
{
  Index i, ibase, ilimit;
  nailboardIndexRange(&ibase, &ilimit, board, 0, base, limit);
  AVER(BTIsResRange(board->level[0], ibase, ilimit));
  BTSetRange(board->level[0], ibase, ilimit);
  for (i = 1; i < board->levels; ++i) {
    nailboardIndexRange(&ibase, &ilimit, board, i, base, limit);
    BTSetRange(board->level[i], ibase, ilimit);
  }
}


/* NailboardIsSetRange -- test if all nails are set in a range
 *
 * Return TRUE if all nails are set in the range between base and
 * limit, or FALSE if any nail is unset. It is an error if any part of
 * the range is not covered by the nailboard.
 *
 * This function is not expected to be efficient because it is only
 * used in an AVER in AMCWhiten to check that the unused part of the
 * buffer for a nailboarded segment has in fact been nailed.
 */

Bool NailboardIsSetRange(Nailboard board, Addr base, Addr limit)
{
  Index ibase, ilimit;
  AVERT(Nailboard, board);
  nailboardIndexRange(&ibase, &ilimit, board, 0, base, limit);
  return BTIsSetRange(board->level[0], ibase, ilimit);
}


/* NailboardIsResRange -- test if all nails are reset in a range
 *
 * Return TRUE if no nails are set in the range between base and
 * limit, or FALSE if any nail is set. It is an error if any part of
 * the range is not covered by the nailboard.
 *
 * This function is on the critical path as it is called for every
 * object in every nailed segment. It must take time that is no more
 * than logarithmic in the size of the range.
 *
 * <design/nailboard#.impl.isresrange>.
 */

Bool NailboardIsResRange(Nailboard board, Addr base, Addr limit)
{
  Index i, ibase, ilimit;
  Index j, jbase, jlimit;
  Addr leftLimit, rightBase;

  AVERT_CRITICAL(Nailboard, board);

  /* Descend levels until ibase and ilimit are two or more bits apart:
   * that is, until there is an "inner" part to the range. */
  i = board->levels;
  do {
    -- i;
    nailboardIndexRange(&ibase, &ilimit, board, i, base, limit);
    if (BTIsResRange(board->level[i], ibase, ilimit))
      /* The entire range was clear. This is expected to be the common
       * case. <design/nailboard#.impl.isresrange.empty> */
      return TRUE;
    if (i == 0)
      /* At level 0 there is only one nail per bit so the set bit is known
       * to be within the range. <design/nailboard#.impl.isresrange.level0> */
      return FALSE;
  } while (ibase + 1 >= ilimit - 1);

  /* At this point we know there is an "inner" part. Are there any
   * bits set in it? <design/nailboard#impl.isresrange.inner> */
  if (!BTIsResRange(board->level[i], ibase + 1, ilimit - 1))
    return FALSE;

  /* At this point we know that in level i, there is is a bit set at
   * ibase or at ilimit - 1 (or both), and everything between them is
   * reset. */
  AVER_CRITICAL(BTGet(board->level[i], ibase)
                || BTGet(board->level[i], ilimit - 1));

  /* Left splinter */
  j = i;
  jbase = ibase;
  for (;;) {
    leftLimit = nailboardAddr(board, j, jbase + 1);
    AVER_CRITICAL(base < leftLimit);
    AVER_CRITICAL(leftLimit < limit);
    -- j;
    nailboardIndexRange(&jbase, &jlimit, board, j, base, leftLimit);
    if (jbase + 1 < jlimit && !BTIsResRange(board->level[j], jbase + 1, jlimit))
      return FALSE;    /* <design/nailboard#.impl.isresrange.inner> */
    if (!BTGet(board->level[j], jbase))
      break;
    if (j == 0)
      return FALSE;
  }

  /* Right splinter */
  j = i;
  jlimit = ilimit;
  for (;;) {
    rightBase = nailboardAddr(board, j, jlimit - 1);
    AVER_CRITICAL(base < rightBase);
    AVER_CRITICAL(rightBase < limit);
    -- j;
    nailboardIndexRange(&jbase, &jlimit, board, j, rightBase, limit);
    if (jbase < jlimit - 1 && !BTIsResRange(board->level[j], jbase, jlimit - 1))
      return FALSE;    /* <design/nailboard#.impl.isresrange.inner> */
    if (!BTGet(board->level[j], jlimit - 1))
      break;
    if (j == 0)
      return FALSE;
  }

  return TRUE;
}


Res NailboardDescribe(Nailboard board, mps_lib_FILE *stream, Count depth)
{
  Index i, j;
  Res res;

  if (!TESTT(Nailboard, board))
    return ResFAIL;
  if (stream == NULL)
    return ResFAIL;

  res = WriteF(stream, depth,
               "Nailboard $P {\n", (WriteFP)board,
               "  levels: $U\n", (WriteFU)board->levels,
               "  newNails: $S\n", WriteFYesNo(board->newNails),
               "  alignShift: $U\n", (WriteFU)board->alignShift,
               NULL);
  if (res != ResOK)
    return res;

  res = RangeDescribe(&board->range, stream, depth + 2);
  if (res != ResOK)
    return res;

  for(i = 0; i < board->levels; ++i) {
    Count levelNails = nailboardLevelBits(nailboardNails(board), i);
    Count resetNails = BTCountResRange(board->level[i], 0, levelNails);
    res = WriteF(stream, depth + 2, "Level $U ($U bits, $U set): ",
                 (WriteFU)i, (WriteFU)levelNails,
                 (WriteFU)(levelNails - resetNails),
                 NULL);
    if (res != ResOK)
      return res;
    for (j = 0; j < levelNails; ++j) {
      char c = BTGet(board->level[i], j) ? '*' : '.';
      res = WriteF(stream, 0, "$C", (WriteFC)c, NULL);
      if (res != ResOK)
        return res;
    }
    res = WriteF(stream, 0, "\n", NULL);
    if (res != ResOK)
      return res;
  }
  res = WriteF(stream, depth, "} Nailboard $P\n", (WriteFP)board, NULL);
  if (res != ResOK)
    return res;

  return ResOK;
}


/* C. COPYRIGHT AND LICENSE
 *
 * Copyright (C) 2014-2020 Ravenbrook Limited <https://www.ravenbrook.com/>.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the
 *    distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
