/* Copyright (C) 2012,2016,2019 Matthew Fluet.
 * Copyright (C) 1999-2008 Henry Cejtin, Matthew Fluet, Suresh
 *    Jagannathan, and Stephen Weeks.
 * Copyright (C) 1997-2000 NEC Research Institute.
 *
 * MLton is released under a HPND-style license.
 * See the file MLton-LICENSE for details.
 */

#if ASSERT
bool isPointerInToSpace (__attribute__((unused)) GC_state s, pointer p, GC_forwardState forwardState) {
 return (not (isPointer (p))
          or (forwardState->toStart <= p and p < forwardState->toLimit));
}

bool isObjptrInToSpace (__attribute__((unused)) GC_state s, objptr op, GC_forwardState forwardState) {
  pointer p;

  if (not (isObjptr (op)))
    return TRUE;
  p = objptrToPointer (op, forwardState->toStart);
  return isPointerInToSpace (s, p, forwardState);
}
#endif

/* getFwdPtrp (p)
 *
 * Returns a pointer to the forwarding pointer for the object pointed to by p.
 */
objptr* getFwdPtrp (pointer p) {
  return (objptr*)(getHeaderp(p));
}

/* getFwdPtr (p)
 *
 * Returns the forwarding pointer for the object pointed to by p.
 */
objptr getFwdPtr (pointer p) {
  return *(getFwdPtrp(p));
}

/* hasFwdPtr (p)
 *
 * Returns true if the object pointed to by p has a valid forwarding pointer.
 */
bool hasFwdPtr (pointer p) {
  return (not (GC_VALID_HEADER_MASK & getHeader(p)));
}

/* forward (s, opp)
 * Forwards the object pointed to by *opp and updates *opp to point to
 * the new object.
 */
void forwardObjptr (GC_state s, objptr *opp, GC_forwardState forwardState) {
  objptr op;
  pointer p;

  op = *opp;
  p = objptrToPointer (op, s->heap.start);
  if (DEBUG_DETAILED)
    fprintf (stderr,
             "forwardObjptr  opp = "FMTPTR"  op = "FMTOBJPTR"  p = "FMTPTR"\n",
             (uintptr_t)opp, op, (uintptr_t)p);
  assert (isObjptrInFromSpace (s, *opp));
  if (DEBUG_DETAILED and hasFwdPtr(p))
    fprintf (stderr, "  already FORWARDED\n");
  if (not (hasFwdPtr(p))) { /* forward the object */
    size_t size, skip;

    size_t metaDataBytes, objectBytes;
    GC_objectTypeTag tag;
    uint16_t bytesNonObjptrs, numObjptrs;

    splitHeader(s, getHeader(p), &tag, NULL, &bytesNonObjptrs, &numObjptrs);

    /* Compute the space taken by the header and object body. */
    if ((NORMAL_TAG == tag) or (WEAK_TAG == tag)) { /* Fixed size object. */
      metaDataBytes = GC_NORMAL_METADATA_SIZE;
      objectBytes = bytesNonObjptrs + (numObjptrs * OBJPTR_SIZE);
      skip = 0;
    } else if (SEQUENCE_TAG == tag) {
      metaDataBytes = GC_SEQUENCE_METADATA_SIZE;
      objectBytes = sizeofSequenceNoMetaData (s, getSequenceLength (p),
                                              bytesNonObjptrs, numObjptrs);
      skip = 0;
    } else { /* Stack. */
      bool current;
      size_t reservedNew;
      GC_stack stack;

      assert (STACK_TAG == tag);
      metaDataBytes = GC_STACK_METADATA_SIZE;
      stack = (GC_stack)p;
      current = getStackCurrent(s) == stack;

      reservedNew = sizeofStackShrinkReserved (s, stack, current);
      if (reservedNew < stack->reserved) {
        if (DEBUG_STACKS or s->controls.messages)
          fprintf (stderr,
                   "[GC: Shrinking stack of size %s bytes to size %s bytes, using %s bytes.]\n",
                   uintmaxToCommaString(stack->reserved),
                   uintmaxToCommaString(reservedNew),
                   uintmaxToCommaString(stack->used));
        stack->reserved = reservedNew;
      }
      objectBytes = sizeof (struct GC_stack) + stack->used;
      skip = stack->reserved - stack->used;
    }
    size = metaDataBytes + objectBytes;
    assert (forwardState->back + size + skip <= forwardState->toLimit);
    /* Copy the object. */
    GC_memcpy (p - metaDataBytes, forwardState->back, size);
    /* If the object has a valid weak pointer, link it into the weaks
     * for update after the copying GC is done.
     */
    if ((WEAK_TAG == tag) and (numObjptrs == 1)) {
      GC_weak w;

      w = (GC_weak)(forwardState->back + GC_NORMAL_METADATA_SIZE + offsetofWeak (s));
      if (DEBUG_WEAK)
        fprintf (stderr, "forwarding weak "FMTPTR" ",
                 (uintptr_t)w);
      if (isObjptr (w->objptr)
          and (not forwardState->amInMinorGC
               or isObjptrInNursery (s, w->objptr))) {
        if (DEBUG_WEAK)
          fprintf (stderr, "linking\n");
        w->link = s->weaks;
        s->weaks = w;
      } else {
        if (DEBUG_WEAK)
          fprintf (stderr, "not linking\n");
      }
    }
    /* Store the forwarding pointer in the old object header. */
    *(getFwdPtrp(p)) = pointerToObjptr (forwardState->back + metaDataBytes,
                                        forwardState->toStart);
    assert (hasFwdPtr(p));
    /* Update the back of the queue. */
    forwardState->back += size + skip;
    assert (isAligned ((size_t)forwardState->back + GC_NORMAL_METADATA_SIZE,
                       s->alignment));
  }
  *opp = getFwdPtr(p);
  if (DEBUG_DETAILED)
    fprintf (stderr,
             "forwardObjptr --> *opp = "FMTPTR"\n",
             (uintptr_t)*opp);
  assert (isObjptrInToSpace (s, *opp, forwardState));
}

void forwardObjptrIfInNursery (GC_state s, objptr *opp, GC_forwardState forwardState) {
  objptr op;
  pointer p;

  op = *opp;
  p = objptrToPointer (op, s->heap.start);
  if ((p < s->heap.nursery) or (p > s->frontier))
    return;
  if (DEBUG_GENERATIONAL)
    fprintf (stderr,
             "forwardObjptrIfInNursery  opp = "FMTPTR"  op = "FMTOBJPTR"  p = "FMTPTR"\n",
             (uintptr_t)opp, op, (uintptr_t)p);
  assert (s->heap.nursery <= p and p < s->limitPlusSlop);
  forwardObjptr (s, opp, forwardState);
}
void forwardObjptrIfInNurseryFun (GC_state s, objptr *opp, void *env) {
  forwardObjptrIfInNursery(s, opp, env);
}

void forwardObjptrIfInHeap (GC_state s, objptr *opp, GC_forwardState forwardState) {
  objptr op;
  pointer p;

  op = *opp;
  p = objptrToPointer (op, s->heap.start);

  if ((p < s->heap.start) or (p > s->frontier))
    return;
  if (DEBUG_DETAILED)
    fprintf (stderr,
             "forwardObjptrIfInHeap  opp = "FMTPTR"  op = "FMTOBJPTR"  p = "FMTPTR"\n",
             (uintptr_t)opp, op, (uintptr_t)p);
  assert (s->heap.start <= p and p < s->limitPlusSlop);
  forwardObjptr (s, opp, forwardState);
}
void forwardObjptrIfInHeapFun (GC_state s, objptr *opp, void *env) {
  forwardObjptrIfInHeap(s, opp, env);
}

/* Walk through all the cards and forward all intergenerational pointers. */
void forwardInterGenerationalObjptrs (GC_state s, GC_forwardState forwardState) {
  GC_cardMapElem *cardMap;
  GC_crossMapElem *crossMap;
  pointer oldGenStart, oldGenEnd;

  size_t cardIndex, maxCardIndex;
  pointer cardStart, cardEnd;
  pointer objectStart;

  struct GC_foreachObjptrClosure forwardObjptrIfInNurseryClosure =
    {.fun = forwardObjptrIfInNurseryFun, .env = forwardState};

  if (DEBUG_GENERATIONAL)
    fprintf (stderr, "Forwarding inter-generational pointers.\n");
  updateCrossMap (s);
  /* Constants. */
  cardMap = s->generationalMaps.cardMap;
  crossMap = s->generationalMaps.crossMap;
  maxCardIndex = sizeToCardMapIndex (align (s->heap.oldGenSize, CARD_SIZE));
  oldGenStart = s->heap.start;
  oldGenEnd = oldGenStart + s->heap.oldGenSize;
  /* Loop variables*/
  objectStart = alignFrontier (s, s->heap.start);
  cardIndex = 0;
  cardStart = oldGenStart;
checkAll:
  assert (cardIndex <= maxCardIndex);
  assert (isFrontierAligned (s, objectStart));
  if (cardIndex == maxCardIndex)
    goto done;
checkCard:
  if (DEBUG_GENERATIONAL)
    fprintf (stderr, "checking card %"PRIuMAX"  objectStart = "FMTPTR"\n",
             (uintmax_t)cardIndex, (uintptr_t)objectStart);
  assert (objectStart < oldGenStart + cardMapIndexToSize (cardIndex + 1));
  if (cardMap[cardIndex]) {
    pointer lastObject;

    s->cumulativeStatistics.numCardsMarked++;
    if (DEBUG_GENERATIONAL)
      fprintf (stderr, "card %"PRIuMAX" is marked  objectStart = "FMTPTR"\n",
               (uintmax_t)cardIndex, (uintptr_t)objectStart);
    assert (isFrontierAligned (s, objectStart));
    cardEnd = cardStart + CARD_SIZE;
    if (oldGenEnd < cardEnd)
      cardEnd = oldGenEnd;
    assert (objectStart < cardEnd);
    lastObject = objectStart;
    /* If we ever add Weak.set, then there could be intergenerational
     * weak pointers, in which case we would need to link the weak
     * objects into s->weaks.  But for now, since there is no
     * Weak.set, the foreachObjptrInRange will do the right thing on
     * weaks, since the weak pointer will never be into the nursery.
     */
    objectStart = foreachObjptrInRange (s, objectStart, &cardEnd,
                                        &forwardObjptrIfInNurseryClosure, FALSE);
    s->cumulativeStatistics.bytesScannedMinor += (uintmax_t)(objectStart - lastObject);
    if (objectStart == oldGenEnd)
      goto done;
    cardIndex = sizeToCardMapIndex ((size_t)(objectStart - oldGenStart));
    cardStart = oldGenStart + cardMapIndexToSize (cardIndex);
    goto checkCard;
  } else {
    unless (CROSS_MAP_EMPTY == crossMap[cardIndex])
      objectStart = cardStart + (size_t)(crossMap[cardIndex] * CROSS_MAP_OFFSET_SCALE);
    if (DEBUG_GENERATIONAL)
      fprintf (stderr,
               "card %"PRIuMAX" is not marked"
               "  crossMap[%"PRIuMAX"] == %"PRIuMAX""
               "  objectStart = "FMTPTR"\n",
               (uintmax_t)cardIndex, (uintmax_t)cardIndex,
               (uintmax_t)(crossMap[cardIndex] * CROSS_MAP_OFFSET_SCALE),
               (uintptr_t)objectStart);
    cardIndex++;
    cardStart += CARD_SIZE;
    goto checkAll;
  }
  assert (FALSE);
done:
  if (DEBUG_GENERATIONAL)
    fprintf (stderr, "Forwarding inter-generational pointers done.\n");
}
