#include "Rts.h"
#include "RtsUtils.h"
#include "NonMoving.h"
#include "NonMovingScav.h"
#include "Capability.h"
#include "Scav.h"
#include "Evac.h"
#include "GCThread.h" // for GCUtils.h
#include "GCUtils.h"
#include "Printer.h"
#include "MarkWeak.h" // scavengeLiveWeak

/*
 * Note [Scavenging the non-moving heap]
 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 * The moving GC promotes objects from the moving heap into the non-moving heap
 * via evacuation and subsequent scavenging. This of course raises the question
 * of how to keep track of which objects still require scavenging. The story
 * here is as follows:
 *
 *  - each GC thread maintains a list, `todo_seg`, of "todo" segments which
 *    still have outstanding scavenging work
 *  - similar to the moving collector, we use the scan pointer of a segment's
 *    block descriptor to track the first yet-to-be-scavenged object.
 *  - the first time we evacuate into an segment during a GC, we push the
 *    segment onto the `todo_seg` list
 *  - scavenge_find_work checks `todo_seg` as a source of scavenging work
 *
 * The scan pointer requires very careful treatment here. Specifically, we must
 * only scavenge objects which we evacuated in the current GC to avoid issues
 * like #21885. objects evacuated to the non-moving generation. In particular,
 * objects can be allocated into the non-moving generation by two ways:
 *
 *  a. evacuation out of from-space by the garbage collector
 *  b. direct allocation by the mutator
 *
 * Like all evacuation, objects moved by (a) must be scavenged, since they
 * may contain references to other objects located in from-space..
 * However, we must not neglect to consider objects allocated by path (b).
 * In short, the problem is that objects directly allocated by the mutator
 * may become unreachable (but not swept, since the containing segment is
 * not yet full), at which point they may contain references to swept objects.
 * Specifically, we observed this in #21885 in the following way:
 *
 * 1. the mutator (specifically in #21885, a `lockCAF`) allocates an object
 *    (specifically a blackhole, which here we will call `blkh`; see Note
 *    [Static objects under the nonmoving collector] for the reason why) on
 *    the non-moving heap. The bitmap of the allocated block remains 0
 *    (since allocation doesn't affect the bitmap) and the containing
 *    segment's (which we will call `blkh_seg`) `next_free` is advanced.
 * 2. We enter the blackhole, evaluating the blackhole to produce a result
 *    (specificaly a cons cell) in the nursery
 * 3. The blackhole gets updated into an indirection pointing to the cons
 *    cell; it is pushed to the generational remembered set
 * 4. we perform a GC, the cons cell is evacuated into the nonmoving heap
 *    (into segment `cons_seg`)
 * 5. the cons cell is marked
 * 6. the GC concludes
 * 7. the CAF and blackhole become unreachable
 * 8. `cons_seg` is filled
 * 9. we start another GC; the cons cell is swept
 * 10. we start a new GC
 * 11. something is evacuated into `blkh_seg`, adding it to the "todo" list
 * 12. we attempt to scavenge `blkh_seg` (namely, all unmarked blocks
 *     between `scan` and `next_free`, which includes `blkh`). We attempt to
 *     evacuate `blkh`'s indirectee, which is the previously-swept cons cell.
 *     This is unsafe, since the indirectee is no longer a valid heap
 *     object.
 *
 * The problem here was that the scavenging logic previously assumed that (a)
 * was the only source of allocations into the non-moving heap and therefore
 * *all* unmarked blocks between `scan` and `next_free` were evacuated.
 * However, due to (b) this is not true, since the scan pointer was only
 * updated (1) when the segment was initialized (to point to block 0),
 * and (2) when an object is scavenged (by advancing it to the next block).
 * Consequently, at the beginning of a GC `scan` may point a block which was
 * allocated by the mutator since the last GC.
 *
 * The solution is to ensure that that the scanned region only encompasses
 * the region of objects allocated for evacuation during the present GC. We do
 * this by updating `scan` as we push the segment to the todo-segment list to
 * point to the block which was evacuated into.
 *
 */

void
nonmovingScavengeOne (StgClosure *q)
{
    ASSERT(LOOKS_LIKE_CLOSURE_PTR(q));
    StgPtr p = (StgPtr)q;
    const StgInfoTable *info = get_itbl(q);
    const bool saved_eager_promotion = gct->eager_promotion;

    switch (info->type) {

    case MVAR_CLEAN:
    case MVAR_DIRTY:
    {
        StgMVar *mvar = ((StgMVar *)p);
        gct->eager_promotion = false;
        evacuate((StgClosure **)&mvar->head);
        evacuate((StgClosure **)&mvar->tail);
        evacuate((StgClosure **)&mvar->value);
        gct->eager_promotion = saved_eager_promotion;
        if (gct->failed_to_evac) {
            mvar->header.info = &stg_MVAR_DIRTY_info;

            // See Note [Dirty flags in the non-moving collector] in NonMoving.c
            markQueuePushClosureGC(&gct->cap->upd_rem_set.queue, (StgClosure *) mvar->head);
            markQueuePushClosureGC(&gct->cap->upd_rem_set.queue, (StgClosure *) mvar->tail);
            markQueuePushClosureGC(&gct->cap->upd_rem_set.queue, (StgClosure *) mvar->value);
        } else {
            mvar->header.info = &stg_MVAR_CLEAN_info;
        }
        break;
    }

    case TVAR:
    {
        StgTVar *tvar = ((StgTVar *)p);
        gct->eager_promotion = false;
        evacuate((StgClosure **)&tvar->current_value);
        evacuate((StgClosure **)&tvar->first_watch_queue_entry);
        gct->eager_promotion = saved_eager_promotion;
        if (gct->failed_to_evac) {
            tvar->header.info = &stg_TVAR_DIRTY_info;

            // See Note [Dirty flags in the non-moving collector] in NonMoving.c
            markQueuePushClosureGC(&gct->cap->upd_rem_set.queue, (StgClosure *) tvar->current_value);
            markQueuePushClosureGC(&gct->cap->upd_rem_set.queue, (StgClosure *) tvar->first_watch_queue_entry);
        } else {
            tvar->header.info = &stg_TVAR_CLEAN_info;
        }
        break;
    }

    case FUN_2_0:
        scavenge_fun_srt(info);
        evacuate(&((StgClosure *)p)->payload[1]);
        evacuate(&((StgClosure *)p)->payload[0]);
        break;

    case THUNK_2_0:
        scavenge_thunk_srt(info);
        evacuate(&((StgThunk *)p)->payload[1]);
        evacuate(&((StgThunk *)p)->payload[0]);
        break;

    case CONSTR_2_0:
        evacuate(&((StgClosure *)p)->payload[1]);
        evacuate(&((StgClosure *)p)->payload[0]);
        break;

    case THUNK_1_0:
        scavenge_thunk_srt(info);
        evacuate(&((StgThunk *)p)->payload[0]);
        break;

    case FUN_1_0:
        scavenge_fun_srt(info);
        FALLTHROUGH;
    case CONSTR_1_0:
        evacuate(&((StgClosure *)p)->payload[0]);
        break;

    case THUNK_0_1:
        scavenge_thunk_srt(info);
        break;

    case FUN_0_1:
        scavenge_fun_srt(info);
        FALLTHROUGH;
    case CONSTR_0_1:
        break;

    case THUNK_0_2:
        scavenge_thunk_srt(info);
        break;

    case FUN_0_2:
        scavenge_fun_srt(info);
        FALLTHROUGH;
    case CONSTR_0_2:
        break;

    case THUNK_1_1:
        scavenge_thunk_srt(info);
        evacuate(&((StgThunk *)p)->payload[0]);
        break;

    case FUN_1_1:
        scavenge_fun_srt(info);
        FALLTHROUGH;
    case CONSTR_1_1:
        evacuate(&q->payload[0]);
        break;

    case FUN:
        scavenge_fun_srt(info);
        goto gen_obj;

    case THUNK:
    {
        scavenge_thunk_srt(info);
        StgPtr end = (P_)((StgThunk *)p)->payload + info->layout.payload.ptrs;
        for (p = (P_)((StgThunk *)p)->payload; p < end; p++) {
            evacuate((StgClosure **)p);
        }
        break;
    }

    case WEAK:
    {
        // We must evacuate the key since it may refer to an object in the
        // moving heap which may be long gone by the time we call
        // nonmovingTidyWeaks.
        StgWeak *weak = (StgWeak *) p;
        gct->eager_promotion = true;
        evacuate(&weak->key);
        gct->eager_promotion = saved_eager_promotion;
        goto gen_obj;
    }

    gen_obj:
    case CONSTR:
    case CONSTR_NOCAF:
    case PRIM:
    {
        StgPtr end = (P_)((StgClosure *)p)->payload + info->layout.payload.ptrs;
        for (p = (P_)((StgClosure *)p)->payload; p < end; p++) {
            evacuate((StgClosure **)p);
        }
        break;
    }

    case BCO: {
        StgBCO *bco = (StgBCO *)p;
        evacuate((StgClosure **)&bco->instrs);
        evacuate((StgClosure **)&bco->literals);
        evacuate((StgClosure **)&bco->ptrs);
        break;
    }

    case MUT_VAR_CLEAN:
    case MUT_VAR_DIRTY: {
        StgMutVar *mv = (StgMutVar *) p;
        gct->eager_promotion = false;
        evacuate(&mv->var);
        gct->eager_promotion = saved_eager_promotion;
        if (gct->failed_to_evac) {
            ((StgClosure *)q)->header.info = &stg_MUT_VAR_DIRTY_info;

            // See Note [Dirty flags in the non-moving collector] in NonMoving.c
            markQueuePushClosureGC(&gct->cap->upd_rem_set.queue, (StgClosure *) mv->var);
        } else {
            ((StgClosure *)q)->header.info = &stg_MUT_VAR_CLEAN_info;
        }
        break;
    }

    case BLOCKING_QUEUE:
    {
        StgBlockingQueue *bq = (StgBlockingQueue *)p;

        gct->eager_promotion = false;
        evacuate(&bq->bh);
        evacuate((StgClosure**)&bq->owner);
        evacuate((StgClosure**)&bq->queue);
        evacuate((StgClosure**)&bq->link);
        gct->eager_promotion = saved_eager_promotion;

        if (gct->failed_to_evac) {
            bq->header.info = &stg_BLOCKING_QUEUE_DIRTY_info;
        } else {
            bq->header.info = &stg_BLOCKING_QUEUE_CLEAN_info;
        }
        break;
    }

    case THUNK_SELECTOR:
    {
        StgSelector *s = (StgSelector *)p;
        evacuate(&s->selectee);
        break;
    }

    // A chunk of stack saved in a heap object
    case AP_STACK:
    {
        StgAP_STACK *ap = (StgAP_STACK *)p;

        evacuate(&ap->fun);
        scavenge_stack((StgPtr)ap->payload, (StgPtr)ap->payload + ap->size);
        break;
    }

    case PAP:
        p = scavenge_PAP((StgPAP *)p);
        break;

    case AP:
        scavenge_AP((StgAP *)p);
        break;

    case ARR_WORDS:
        // nothing to follow
        break;

    case MUT_ARR_PTRS_CLEAN:
    case MUT_ARR_PTRS_DIRTY:
    {
        gct->eager_promotion = false;
        scavenge_mut_arr_ptrs((StgMutArrPtrs*)p);
        gct->eager_promotion = saved_eager_promotion;
        if (gct->failed_to_evac) {
            ((StgClosure *)q)->header.info = &stg_MUT_ARR_PTRS_DIRTY_info;
        } else {
            ((StgClosure *)q)->header.info = &stg_MUT_ARR_PTRS_CLEAN_info;
        }
        gct->failed_to_evac = true; // always put it on the mutable list.
        break;
    }

    case MUT_ARR_PTRS_FROZEN_CLEAN:
    case MUT_ARR_PTRS_FROZEN_DIRTY:
        // follow everything
    {
        scavenge_mut_arr_ptrs((StgMutArrPtrs*)p);

        if (gct->failed_to_evac) {
            ((StgClosure *)q)->header.info = &stg_MUT_ARR_PTRS_FROZEN_DIRTY_info;
        } else {
            ((StgClosure *)q)->header.info = &stg_MUT_ARR_PTRS_FROZEN_CLEAN_info;
        }
        break;
    }

    case SMALL_MUT_ARR_PTRS_CLEAN:
    case SMALL_MUT_ARR_PTRS_DIRTY:
        // follow everything
    {
        StgPtr next = p + small_mut_arr_ptrs_sizeW((StgSmallMutArrPtrs*)p);
        gct->eager_promotion = false;
        for (p = (P_)((StgSmallMutArrPtrs *)p)->payload; p < next; p++) {
            evacuate((StgClosure **)p);
        }
        gct->eager_promotion = saved_eager_promotion;

        if (gct->failed_to_evac) {
            ((StgClosure *)q)->header.info = &stg_SMALL_MUT_ARR_PTRS_DIRTY_info;
        } else {
            ((StgClosure *)q)->header.info = &stg_SMALL_MUT_ARR_PTRS_CLEAN_info;
        }
        gct->failed_to_evac = true; // always put it on the mutable list.
        break;
    }

    case SMALL_MUT_ARR_PTRS_FROZEN_CLEAN:
    case SMALL_MUT_ARR_PTRS_FROZEN_DIRTY:
        // follow everything
    {
        StgPtr next = p + small_mut_arr_ptrs_sizeW((StgSmallMutArrPtrs*)p);
        for (p = (P_)((StgSmallMutArrPtrs *)p)->payload; p < next; p++) {
            evacuate((StgClosure **)p);
        }

        if (gct->failed_to_evac) {
            ((StgClosure *)q)->header.info = &stg_SMALL_MUT_ARR_PTRS_FROZEN_DIRTY_info;
        } else {
            ((StgClosure *)q)->header.info = &stg_SMALL_MUT_ARR_PTRS_FROZEN_CLEAN_info;
        }
        break;
    }

    case TSO:
    {
        scavengeTSO((StgTSO *)p);
        break;
    }

    case STACK:
    {
        StgStack *stack = (StgStack*)p;

        gct->eager_promotion = false;
        scavenge_stack(stack->sp, stack->stack + stack->stack_size);
        gct->eager_promotion = saved_eager_promotion;
        stack->dirty = gct->failed_to_evac;
        break;
    }

    case MUT_PRIM:
    {
        StgPtr end = (P_)((StgClosure *)p)->payload + info->layout.payload.ptrs;
        gct->eager_promotion = false;
        for (p = (P_)((StgClosure *)p)->payload; p < end; p++) {
            evacuate((StgClosure **)p);
        }
        gct->eager_promotion = saved_eager_promotion;
        gct->failed_to_evac = true; // mutable
        break;
    }

    case TREC_CHUNK:
      {
        StgWord i;
        StgTRecChunk *tc = ((StgTRecChunk *) p);
        TRecEntry *e = &(tc -> entries[0]);
        gct->eager_promotion = false;
        evacuate((StgClosure **)&tc->prev_chunk);
        for (i = 0; i < tc -> next_entry_idx; i ++, e++ ) {
          evacuate((StgClosure **)&e->tvar);
          evacuate((StgClosure **)&e->expected_value);
          evacuate((StgClosure **)&e->new_value);
        }
        gct->eager_promotion = saved_eager_promotion;
        gct->failed_to_evac = true; // mutable
        break;
      }

    case IND:
    case BLACKHOLE:
    case IND_STATIC:
        evacuate(&((StgInd *)p)->indirectee);
        break;

    case COMPACT_NFDATA:
        scavenge_compact((StgCompactNFData*)p);
        break;

    case CONTINUATION:
        scavenge_continuation((StgContinuation *)p);
        break;

    default:
        barf("nonmoving scavenge: unimplemented/strange closure type %d @ %p",
             info->type, p);
    }

    if (gct->failed_to_evac) {
        // Mutable object or points to a younger object, add to the mut_list
        gct->failed_to_evac = false;
        if (oldest_gen->no > 0) {
            recordMutableGen_GC(q, oldest_gen->no);
        }
    }
}

/* Scavenge objects evacuated into a nonmoving segment by a minor GC */
void
scavengeNonmovingSegment (struct NonmovingSegment *seg)
{
    const StgWord blk_size = nonmovingSegmentBlockSize(seg);
    gct->evac_gen_no = oldest_gen->no;
    gct->failed_to_evac = false;

    // scavenge objects between scan and free_ptr whose bitmap bits are 0
    bdescr *seg_block = Bdescr((P_)seg);

    ASSERT(seg_block->u.scan >= (P_)nonmovingSegmentGetBlock(seg, 0));
    ASSERT(seg_block->u.scan <= (P_)nonmovingSegmentGetBlock(seg, seg->next_free));

    StgPtr scan = seg_block->u.scan;
    StgPtr scan_end = (P_)nonmovingSegmentGetBlock(seg, seg->next_free);
    if (scan == scan_end)
        return;

    // At this point the segment is not on the todo_seg list. Consequently, we
    // may need to re-add it during scavenging (as we may evacuate a new object
    // to this segment); this has the effect of updating the scan pointer.
    // We must therefore take care to move the scan pointer to the end of the
    // scanned region *before* doing any scavenging.
    seg_block->u.scan = scan_end;

    nonmoving_block_idx p_idx = nonmovingGetBlockIdx(scan);
    while (scan < scan_end) {
        StgClosure *p = (StgClosure*)scan;

        // bit set = was allocated in a previous GC, no need to scavenge
        // bit not set = new allocation, so scavenge
        if (nonmovingGetMark(seg, p_idx) == 0) {
            nonmovingScavengeOne(p);
        }

        scan = (StgPtr) ((uint8_t*) scan + blk_size);
        p_idx++;
    }
}
