#include "moar.h"

/* Adds a location holding a collectable object to the permanent list of GC
 * roots, so that it will always be marked and never die. Note that the
 * address of the collectable must be passed, since it will need to be
 * updated. */
void MVM_gc_root_add_permanent_desc(MVMThreadContext *tc, MVMCollectable **obj_ref, const char *description) {
    if (obj_ref == NULL)
        MVM_panic(MVM_exitcode_gcroots, "Illegal attempt to add null object address as a permanent root");

    uv_mutex_lock(&tc->instance->mutex_permroots);
    /* Allocate extra permanent root space if needed. */
    if (tc->instance->num_permroots == tc->instance->alloc_permroots) {
        tc->instance->alloc_permroots *= 2;
        tc->instance->permroots = MVM_realloc(tc->instance->permroots,
            sizeof(MVMCollectable **) * tc->instance->alloc_permroots);
        tc->instance->permroot_descriptions = MVM_realloc(
            tc->instance->permroot_descriptions,
            sizeof(char *) * tc->instance->alloc_permroots);
    }

    /* Add this one to the list. */
    tc->instance->permroots[tc->instance->num_permroots] = obj_ref;
    tc->instance->permroot_descriptions[tc->instance->num_permroots] = description;
    tc->instance->num_permroots++;

    uv_mutex_unlock(&tc->instance->mutex_permroots);
}

void MVM_gc_root_add_permanent(MVMThreadContext *tc, MVMCollectable **obj_ref) {
    MVM_gc_root_add_permanent_desc(tc, obj_ref, "<\?\?>");
}

/* Adds the set of permanently registered roots to a GC worklist. */
void MVM_gc_root_add_permanents_to_worklist(MVMThreadContext *tc, MVMGCWorklist *worklist, MVMHeapSnapshotState *snapshot) {
    MVMuint32         i, num_roots;
    MVMCollectable ***permroots;
    num_roots = tc->instance->num_permroots;
    permroots = tc->instance->permroots;
    if (worklist) {
        for (i = 0; i < num_roots; i++)
            MVM_gc_worklist_add(tc, worklist, permroots[i]);
    }
    else {
        const char **permroot_descriptions = tc->instance->permroot_descriptions;
        for (i = 0; i < num_roots; i++)
            MVM_profile_heap_add_collectable_rel_const_cstr(tc, snapshot,
                *(permroots[i]), permroot_descriptions[i]);
    }
}

/* This macro factors out the logic to check if we're adding to a GC worklist
 * or a heap snapshot, and does the appropriate thing. */
#define add_collectable(tc, worklist, snapshot, col, desc) \
    do { \
        if (worklist) { \
            MVM_gc_worklist_add(tc, worklist, &(col)); \
        } \
        else { \
            MVM_profile_heap_add_collectable_rel_const_cstr(tc, snapshot, \
                (MVMCollectable *)col, desc); \
        } \
    } while (0)

/* Adds anything that is a root thanks to being referenced by instance,
 * but that isn't permanent. */
void MVM_gc_root_add_instance_roots_to_worklist(MVMThreadContext *tc, MVMGCWorklist *worklist, MVMHeapSnapshotState *snapshot) {
    MVMString                  **int_to_str_cache;
    MVMuint32                    i;

    add_collectable(tc, worklist, snapshot, tc->instance->threads, "Thread list");
    add_collectable(tc, worklist, snapshot, tc->instance->compiler_registry, "Compiler registry");
    add_collectable(tc, worklist, snapshot, tc->instance->hll_syms, "HLL symbols");
    add_collectable(tc, worklist, snapshot, tc->instance->clargs, "Command line args");

    add_collectable(tc, worklist, snapshot, tc->instance->event_loop_thread,
        "Event loop thread");
    add_collectable(tc, worklist, snapshot, tc->instance->event_loop_todo_queue,
        "Event loop todo queue");
    add_collectable(tc, worklist, snapshot, tc->instance->event_loop_permit_queue,
        "Event loop permit queue");
    add_collectable(tc, worklist, snapshot, tc->instance->event_loop_cancel_queue,
        "Event loop cancel queue");
    add_collectable(tc, worklist, snapshot, tc->instance->event_loop_active,
        "Event loop active task list");
    add_collectable(tc, worklist, snapshot, tc->instance->event_loop_free_indices,
        "Event loop active free indices list");

    add_collectable(tc, worklist, snapshot, tc->instance->spesh_thread,
        "Specialization thread");
    add_collectable(tc, worklist, snapshot, tc->instance->spesh_queue,
        "Specialization log queue");

    if (worklist)
        MVM_spesh_plan_gc_mark(tc, tc->instance->spesh_plan, worklist);

    int_to_str_cache = tc->instance->int_to_str_cache;
    for (i = 0; i < MVM_INT_TO_STR_CACHE_SIZE; i++)
        add_collectable(tc, worklist, snapshot, int_to_str_cache[i],
            "Integer to string cache entry");

    /* okay, so this makes the weak hash slightly less weak.. for certain
     * keys of it anyway... */
    MVMStrHashTable *const weakhash = &tc->instance->sc_weakhash;
    MVMStrHashIterator iterator = MVM_str_hash_first(tc, weakhash);
    while (!MVM_str_hash_at_end(tc, weakhash, iterator)) {
        struct MVMSerializationContextWeakHashEntry *current
            = MVM_str_hash_current_nocheck(tc, weakhash, iterator);
        /* mark the string handle pointer iff it hasn't yet been resolved */
        add_collectable(tc, worklist, snapshot, current->hash_handle.key,
            "SC weakhash hash key");
        if (!current->scb->sc)
            add_collectable(tc, worklist, snapshot, current->scb->handle,
                "SC weakhash unresolved handle");
        else if (!current->scb->claimed)
            add_collectable(tc, worklist, snapshot, current->scb->sc,
                "SC weakhash unclaimed SC");
        iterator = MVM_str_hash_next_nocheck(tc, weakhash, iterator);
    }

    add_collectable(tc, worklist, snapshot, tc->instance->cached_backend_config,
        "Cached backend configuration hash");

    add_collectable(tc, worklist, snapshot, tc->instance->env_hash,
        "Cached environment variable hash");

    add_collectable(tc, worklist, snapshot, tc->instance->sig_arr,
        "Cached signal mapping array");

    if (tc->instance->confprog)
        MVM_confprog_mark(tc, worklist, snapshot);

    add_collectable(tc, worklist, snapshot, tc->instance->subscriptions.subscription_queue,
        "VM Event Subscription Queue");

    add_collectable(tc, worklist, snapshot, tc->instance->subscriptions.GCEvent,
        "VM Event GCEvent type");
    add_collectable(tc, worklist, snapshot, tc->instance->subscriptions.GCEvent,
        "VM Event SpeshOverviewEvent type");

    MVM_callsite_mark_interns(tc, worklist, snapshot);

    if (worklist)
        MVM_disp_registry_mark(tc, worklist);
    else
        MVM_disp_registry_describe(tc, snapshot);

    MVM_debugserver_mark_handles(tc, worklist, snapshot);
}

/* Adds anything that is a root thanks to being referenced by a thread,
 * context, but that isn't permanent. */
void MVM_gc_root_add_tc_roots_to_worklist(MVMThreadContext *tc, MVMGCWorklist *worklist, MVMHeapSnapshotState *snapshot) {
    /* The call stack. */
    MVM_callstack_mark_current_thread(tc, worklist, snapshot);

    /* The current frame, if it's on the heap. */
    if (tc->cur_frame && !MVM_FRAME_IS_ON_CALLSTACK(tc, tc->cur_frame))
        add_collectable(tc, worklist, snapshot, tc->cur_frame, "Current bytecode frame");

    /* Any active exception handlers and payload. */
    MVMActiveHandler *cur_ah = tc->active_handlers;
    while (cur_ah != NULL) {
        add_collectable(tc, worklist, snapshot, cur_ah->ex_obj, "Active handler exception object");
        if (!MVM_FRAME_IS_ON_CALLSTACK(tc, cur_ah->frame))
            add_collectable(tc, worklist, snapshot, cur_ah->frame, "Active handler frame");
        cur_ah = cur_ah->next_handler;
    }
    add_collectable(tc, worklist, snapshot, tc->last_payload,
        "Last exception payload");

    /* The thread object. */
    add_collectable(tc, worklist, snapshot, tc->thread_obj, "Thread object");

    /* The thread's entry frame. */
    if (tc->thread_entry_frame && !MVM_FRAME_IS_ON_CALLSTACK(tc, tc->thread_entry_frame))
        add_collectable(tc, worklist, snapshot, tc->thread_entry_frame, "Thread entry frame");

    /* Any exception handler result. */
    add_collectable(tc, worklist, snapshot, tc->last_handler_result, "Last handler result");

    /* List of SCs currently being compiled. */
    add_collectable(tc, worklist, snapshot, tc->compiling_scs, "Compiling serialization contexts");

    /* compunit variable pointer (and be null if thread finished) */
    if (tc->interp_cu)
        add_collectable(tc, worklist, snapshot, *(tc->interp_cu), "Current interpreter compilation unit");

    /* Callback cache. */
    MVMStrHashTable *cache = &tc->native_callback_cache;
    MVMStrHashIterator iterator = MVM_str_hash_first(tc, cache);
    while (!MVM_str_hash_at_end(tc, cache, iterator)) {
        struct MVMNativeCallbackCacheHead *current_cbceh
            = MVM_str_hash_current_nocheck(tc, cache, iterator);
        MVMint32 i;
        MVMNativeCallback *entry = current_cbceh->head;
        add_collectable(tc, worklist, snapshot, current_cbceh->hash_handle.key,
                        "Native callback cache key");
        while (entry) {
            for (i = 0; i < entry->num_types; i++)
                add_collectable(tc, worklist, snapshot, entry->types[i],
                                "Native callback cache type");
            add_collectable(tc, worklist, snapshot, entry->target,
                            "Native callback cache target");
            entry = entry->next;
        }
        iterator = MVM_str_hash_next_nocheck(tc, cache, iterator);
    }

    /* Profiling data. */
    if (worklist)
        MVM_profile_instrumented_mark_data(tc, worklist);

    /* Specialization log and stack simulation state. */
    add_collectable(tc, worklist, snapshot, tc->spesh_log, "Specialization log");
    if (worklist)
        MVM_spesh_sim_stack_gc_mark(tc, tc->spesh_sim_stack, worklist);
    else
        MVM_spesh_sim_stack_gc_describe(tc, snapshot, tc->spesh_sim_stack);
    if (tc->spesh_active_graph) {
        if (worklist)
            MVM_spesh_graph_mark(tc, tc->spesh_active_graph, worklist);
        else
            MVM_spesh_graph_describe(tc, tc->spesh_active_graph, snapshot);
    }
    add_collectable(tc, worklist, snapshot, tc->osr_hunt_static_frame,
            "Current static frame for watching for OSR spesh candidate");

    if (tc->step_mode_frame)
        add_collectable(tc, worklist, snapshot, tc->step_mode_frame,
                "Frame referenced for stepping mode");

    if (tc->mark_args) {
        for (int from = 0; from < tc->mark_args->callsite->flag_count; from++) {
            if ((tc->mark_args->callsite->arg_flags[from] & MVM_CALLSITE_ARG_TYPE_MASK) == MVM_CALLSITE_ARG_OBJ
                || (tc->mark_args->callsite->arg_flags[from] & MVM_CALLSITE_ARG_TYPE_MASK) == MVM_CALLSITE_ARG_STR)
                MVM_gc_worklist_add(tc, worklist, &tc->mark_args->source[tc->mark_args->map[from]].o);
        }
    }
}

/* Pushes a temporary root onto the thread-local roots list. */
void MVM_gc_root_temp_push_slow(MVMThreadContext *tc, MVMCollectable **obj_ref) {
    /* Allocate extra temporary root space if needed. */
    if (tc->num_temproots == tc->alloc_temproots) {
        tc->alloc_temproots *= 2;
        tc->temproots = MVM_realloc(tc->temproots,
            sizeof(MVMCollectable **) * tc->alloc_temproots);
    }

    /* Add this one to the list. */
    tc->temproots[tc->num_temproots] = obj_ref;
    tc->num_temproots++;
}

/* Marks the temporary root stack at its current height as the limit for
 * removing all roots. This is done so that in nested interpreter runs
 * (at present, just nativecall callbacks) we don't clear things that
 * are pushed by the native call itself. */
MVMuint32 MVM_gc_root_temp_mark(MVMThreadContext *tc) {
    MVMint32 current = tc->mark_temproots;
    tc->mark_temproots = tc->num_temproots;
    return current;
}

/* Resets the temporary root stack mark to the provided height. */
void MVM_gc_root_temp_mark_reset(MVMThreadContext *tc, MVMuint32 mark) {
    tc->mark_temproots = mark;
}

/* Pops all temporary roots off the thread-local roots list. */
void MVM_gc_root_temp_pop_all(MVMThreadContext *tc) {
    tc->num_temproots = tc->mark_temproots;
}

/* Adds the set of thread-local temporary roots to a GC worklist. Note that we
 * may MVMROOT things that are actually frames on a therad local call stack as
 * they may be GC-able; check for this and make sure such roots do not get
 * added to the worklist. (Cheaper to do it here in the event we GC than to
 * do it on every stack push). */
static MVMuint32 is_stack_frame(MVMThreadContext *tc, MVMCollectable **c) {
    MVMCollectable *maybe_frame = *c;
    return maybe_frame && maybe_frame->flags1 == 0 && maybe_frame->owner == 0;
}
void MVM_gc_root_add_temps_to_worklist(MVMThreadContext *tc, MVMGCWorklist *worklist, MVMHeapSnapshotState *snapshot) {
    MVMuint32         i, num_roots;
    MVMCollectable ***temproots;
    num_roots = tc->num_temproots;
    temproots = tc->temproots;
    if (worklist) {
        for (i = 0; i < num_roots; i++)
            if (!is_stack_frame(tc, temproots[i]))
                MVM_gc_worklist_add(tc, worklist, temproots[i]);
    }
    else {
        for (i = 0; i < num_roots; i++)
            if (!is_stack_frame(tc, temproots[i]))
                MVM_profile_heap_add_collectable_rel_idx(tc, snapshot, *(temproots[i]), i);
    }
}

/* Pushes a collectable that is in generation 2, but now references a nursery
 * collectable, into the gen2 root set. */
void MVM_gc_root_gen2_add(MVMThreadContext *tc, MVMCollectable *c) {
    /* Ensure the collectable is not null. */
    if (c == NULL)
        MVM_panic(MVM_exitcode_gcroots, "Illegal attempt to add null collectable address as an inter-generational root");
    assert(!(c->flags2 & MVM_CF_FORWARDER_VALID));

    /* Allocate extra gen2 aggregate space if needed. */
    if (tc->num_gen2roots == tc->alloc_gen2roots) {
        tc->alloc_gen2roots *= 2;
        tc->gen2roots = MVM_realloc(tc->gen2roots,
            sizeof(MVMCollectable *) * tc->alloc_gen2roots);
    }

    /* Add this one to the list. */
    tc->gen2roots[tc->num_gen2roots] = c;
    tc->num_gen2roots++;

    /* Flag it as added, so we don't add it multiple times. */
    c->flags2 |= MVM_CF_IN_GEN2_ROOT_LIST;
}

/* Adds the set of thread-local inter-generational roots to a GC worklist. As
 * a side-effect, removes gen2 roots that no longer point to any nursery
 * items (usually because all the referenced objects also got promoted). */
void MVM_gc_root_add_gen2s_to_worklist(MVMThreadContext *tc, MVMGCWorklist *worklist) {
    MVMCollectable **gen2roots = tc->gen2roots;
    MVMuint32        num_roots = tc->num_gen2roots;
    MVMuint32        i;

    /* We'll remove some entries from this list. The algorithm is simply to
     * slide all that stay towards the start of the array. */
    MVMuint32 insert_pos = 0;

    /* Guess that we'll end up with around num_roots entries, to avoid some
     * worklist growth reallocations. */
    MVM_gc_worklist_presize_for(tc, worklist, num_roots);

    /* Visit each gen2 root and... */
    for (i = 0; i < num_roots; i++) {
        /* Count items on worklist before we mark it. */
        MVMuint32 items_before_mark  = worklist->items;

        /* Put things it references into the worklist; since the worklist will
         * be set not to include gen2 things, only nursery things will make it
         * in. */
        assert(!(gen2roots[i]->flags2 & MVM_CF_FORWARDER_VALID));
        MVM_gc_mark_collectable(tc, worklist, gen2roots[i]);

        /* If we added any nursery objects, or if we are a frame with ->work
         * area, keep in this list. */
        if (worklist->items != items_before_mark ||
                (gen2roots[i]->flags1 & MVM_CF_FRAME && ((MVMFrame *)gen2roots[i])->work)) {
            gen2roots[insert_pos] = gen2roots[i];
            insert_pos++;
        }

        /* Otherwise, clear the "in gen2 root list" flag. Note that another
         * thread may also clear this flag if it also had the entry in its
         * inter-gen list, so be careful to clear it, not just toggle. */
        else {
            gen2roots[i]->flags2 &= ~MVM_CF_IN_GEN2_ROOT_LIST;
        }
    }

    /* New number of entries after sliding is the final insert position. */
    tc->num_gen2roots = insert_pos;
}

/* Adds inter-generational roots to a heap snapshot. */
void MVM_gc_root_add_gen2s_to_snapshot(MVMThreadContext *tc, MVMHeapSnapshotState *snapshot) {
    MVMCollectable **gen2roots = tc->gen2roots;
    MVMuint32        num_roots = tc->num_gen2roots;
    MVMuint32        i;
    for (i = 0; i < num_roots; i++)
        MVM_profile_heap_add_collectable_rel_idx(tc, snapshot, gen2roots[i], i);
}

/* Visits all of the roots in the gen2 list and removes those that have been
 * collected. Applied after a full collection. */
void MVM_gc_root_gen2_cleanup(MVMThreadContext *tc) {
    MVMCollectable **gen2roots    = tc->gen2roots;
    MVMuint32        num_roots    = tc->num_gen2roots;
    MVMuint32        i = 0;
    MVMuint32        cur_survivor;

    /* Find the first collected object. */
    while (i < num_roots && gen2roots[i]->flags2 & MVM_CF_GEN2_LIVE)
        i++;
    cur_survivor = i;

    /* Slide others back so the alive ones are at the start of the list. */
    while (i < num_roots) {
        if (gen2roots[i]->flags2 & MVM_CF_GEN2_LIVE) {
            assert(!(gen2roots[i]->flags2 & MVM_CF_FORWARDER_VALID));
            gen2roots[cur_survivor++] = gen2roots[i];
        }
        i++;
    }

    tc->num_gen2roots = cur_survivor;
}

/* Walks frames and compilation units. Adds the roots it finds into the
 * GC worklist. */
static void scan_lexicals(MVMThreadContext *tc, MVMGCWorklist *worklist, MVMFrame *frame);
void MVM_gc_root_add_frame_roots_to_worklist(MVMThreadContext *tc, MVMGCWorklist *worklist, MVMFrame *cur_frame) {
    /* Add caller to worklist if it's heap-allocated. */
    if (cur_frame->caller && !MVM_FRAME_IS_ON_CALLSTACK(tc, cur_frame->caller))
        MVM_gc_worklist_add(tc, worklist, &cur_frame->caller);

    /* Add outer, code_ref and static info to work list. */
    MVM_gc_worklist_add(tc, worklist, &cur_frame->outer);
    MVM_gc_worklist_add(tc, worklist, &cur_frame->code_ref);
    MVM_gc_worklist_add(tc, worklist, &cur_frame->static_info);
    MVM_gc_worklist_add(tc, worklist, &cur_frame->spesh_cand);

    /* Mark frame extras if needed. */
    if (cur_frame->extra) {
        MVMFrameExtra *e = cur_frame->extra;
        if (e->dynlex_cache_name)
            MVM_gc_worklist_add(tc, worklist, &e->dynlex_cache_name);
        MVM_gc_worklist_add(tc, worklist, &e->exit_handler_result);
    }

    /* Scan the registers. */
    MVM_gc_root_add_frame_registers_to_worklist(tc, worklist, cur_frame);
    scan_lexicals(tc, worklist, cur_frame);
}

/* Takes a frame, scans its registers and adds them to the roots. */
void MVM_gc_root_add_frame_registers_to_worklist(MVMThreadContext *tc, MVMGCWorklist *worklist, MVMFrame *frame) {
    MVMuint16  i, count;
    MVMuint16 *type_map;
    /* We only need to do any of this work if the frame is in dynamic scope. */
    if (frame->work) {
        /* Scan locals. */

        MVMSpeshCandidate *spesh_cand = frame->spesh_cand;
        MVMJitCode *jitcode = spesh_cand ? spesh_cand->body.jitcode : NULL;
        if (jitcode && jitcode->local_types) {
            type_map = jitcode->local_types;
            count    = jitcode->num_locals;
        } else if (spesh_cand && spesh_cand->body.local_types) {
            type_map = spesh_cand->body.local_types;
            count    = spesh_cand->body.num_locals;
        }
        else {
            type_map = frame->static_info->body.local_types;
            count    = frame->static_info->body.num_locals;
        }
        for (i = 0; i < count; i++)
            if (type_map[i] == MVM_reg_str || type_map[i] == MVM_reg_obj)
                MVM_gc_worklist_add(tc, worklist, &frame->work[i].o);
    }
}

/* Takes a frame, scans its lexicals and adds them to the roots. */
static void scan_lexicals(MVMThreadContext *tc, MVMGCWorklist *worklist, MVMFrame *frame) {
    if (frame->env) {
        MVMuint16  i, count;
        MVMuint16 *type_map;
        if (frame->spesh_cand && frame->spesh_cand->body.lexical_types) {
            type_map = frame->spesh_cand->body.lexical_types;
            count    = frame->spesh_cand->body.num_lexicals;
        }
        else {
            type_map = frame->static_info->body.lexical_types;
            count    = frame->static_info->body.num_lexicals;
        }
        for (i = 0; i < count; i++)
            if (type_map[i] == MVM_reg_str || type_map[i] == MVM_reg_obj)
                MVM_gc_worklist_add(tc, worklist, &frame->env[i].o);
    }
}
