/* A worklist holds the current set of pointers we have got in the queue
 * to scan. We hide away the details of it behind this abstraction since
 * the order in which we hand things back from it can have a big influence
 * on locality in the copied/compacted heap, and thus mutator performance.
 *
 * For the time being, we do the simplest possible thing: just make it a
 * stack. This actually has reasonable properties if we scan objects right
 * after they are copied/moved, since an object's children will then come
 * right after the object itself - unless they were copied/moved earlier.
 * But things aren't quite so rosy for deeply nested data structures, where
 * two siblings may thus end up far apart. Lots of stuff in the literature
 * on these issues, but for now this is probably less bad than some of the
 * other options.
 */
struct MVMGCWorklist {
    /* The worklist itself. An array of addresses which hold pointers to
     * collectables (yes, two levels of indirection, since we need to
     * update addresses in copying/moving algorithms.) */
    MVMCollectable ***list;

    /* The number of items on the worklist. */
    MVMuint32 items;

    /* The number of items the work list is allocated to hold. */
    MVMuint32 alloc;

    /* Whether we should include gen2 entries. */
    MVMuint8 include_gen2;
};

/* Some macros for doing stuff fast with worklists, defined to look like
 * functions since perhaps they become them in the future if needed. */
#if MVM_GC_DEBUG
#define MVM_gc_worklist_add(tc, worklist, item) \
    do { \
        MVMCollectable **item_to_add = (MVMCollectable **)(item);\
        if (*item_to_add) { \
            if ((*item_to_add)->owner == 0) \
                MVM_panic(1, "Zeroed owner in item added to GC worklist"); \
            if ((*item_to_add)->owner > tc->instance->next_user_thread_id) \
                MVM_panic(1, "Invalid owner in item added to GC worklist"); \
            if ((*item_to_add)->flags2 & MVM_CF_DEBUG_IN_GEN2_FREE_LIST) \
                MVM_panic(1, "Adding item to worklist already freed in gen2\n"); \
            if ((*item_to_add)->flags1 & MVM_CF_FRAME) {\
                if (!((MVMFrame *)(*item_to_add))->static_info) \
                    MVM_panic(1, "Frame with NULL static_info added to worklist"); \
            }\
            else if (((*item_to_add)->flags1 & MVM_CF_STABLE) == 0 && !STABLE(*item_to_add)) \
                MVM_panic(1, "NULL STable in item added to GC worklist"); \
            if ((char *)*item_to_add >= (char *)tc->nursery_alloc && \
                    (char *)*item_to_add < (char *)tc->nursery_alloc_limit) \
                MVM_panic(1, "Adding pointer %p to past fromspace to GC worklist", \
                    *item_to_add); \
        } \
        if (*item_to_add && (worklist->include_gen2 || !((*item_to_add)->flags2 & MVM_CF_SECOND_GEN))) { \
            if (worklist->items == worklist->alloc) \
                MVM_gc_worklist_add_slow(tc, worklist, item_to_add); \
            else \
                worklist->list[worklist->items++] = item_to_add; \
        } \
    } while (0)
#define MVM_gc_worklist_add_include_gen2_nocheck(tc, worklist, item) \
    MVM_gc_worklist_add(tc, worklist, item)
#define MVM_gc_worklist_add_no_include_gen2_nocheck(tc, worklist, item) \
    MVM_gc_worklist_add(tc, worklist, item)
#define MVM_gc_worklist_add_object_no_include_gen2_nocheck(tc, worklist, item) \
    MVM_gc_worklist_add(tc, worklist, item)
#else
#define MVM_gc_worklist_add(tc, worklist, item) \
    do { \
        MVMCollectable **item_to_add = (MVMCollectable **)(item);\
        if (*item_to_add && (worklist->include_gen2 || !((*item_to_add)->flags2 & MVM_CF_SECOND_GEN))) { \
            if (worklist->items == worklist->alloc) \
                MVM_gc_worklist_add_slow(tc, worklist, item_to_add); \
            else \
                worklist->list[worklist->items++] = item_to_add; \
        } \
    } while (0)
/* Assumes worklist->include_gen2 is True. Also assumes there is enough space
 * in worklist->items.
 * Make sure to call MVM_gc_worklist_presize_for() and that worklist->include_gen2
 * is True before calling this macro. */
#define MVM_gc_worklist_add_include_gen2_nocheck(tc, worklist, item) \
do { \
    worklist->list[worklist->items++] = (MVMCollectable**)item; \
} while (0)
/* Assumes worklist->include_gen2 is False. Also assumes there is enough space
 * in worklist->items.
 * Make sure to call MVM_gc_worklist_presize_for() and that worklist->include_gen2
 * is False before calling this macro. */
#define MVM_gc_worklist_add_no_include_gen2_nocheck(tc, worklist, item) \
do { \
    if (*item && !( (*(MVMCollectable**)item)->flags2 & MVM_CF_SECOND_GEN)) { \
        worklist->list[worklist->items++] = (MVMCollectable**)item; \
    } \
} while (0)
#define MVM_gc_worklist_add_object_no_include_gen2_nocheck(tc, worklist, object) \
do { \
    if (*object && !( (*object)->header.flags2 & MVM_CF_SECOND_GEN)) { \
        worklist->list[worklist->items++] = (MVMCollectable**)object; \
    } \
} while (0)
#endif

#define MVM_gc_worklist_get(tc, worklist) \
    (worklist->items ? \
        worklist->list[--worklist->items] : \
        NULL)

/* Various functions for worklist manipulation. */
MVMGCWorklist * MVM_gc_worklist_create(MVMThreadContext *tc, MVMuint8 include_gen2);
MVM_PUBLIC void MVM_gc_worklist_add_slow(MVMThreadContext *tc, MVMGCWorklist *worklist, MVMCollectable **item);
void MVM_gc_worklist_presize_for(MVMThreadContext *tc, MVMGCWorklist *worklist, MVMint32 items);
void MVM_gc_worklist_destroy(MVMThreadContext *tc, MVMGCWorklist *worklist);

/* The number of pointers we assume the list may need to hold initially;
 * it will be resized as needed. */
#define MVM_GC_WORKLIST_START_SIZE      256
