/* GC private-use list allocator.
 *
 * A private-use list is a list of private-uses conses,
 * which are just like ordinary conses except that the cdrs
 * do not have a pointer lowtag and the last cdr is 0, not NIL.
 *
 * With gencgc we steal a GC card having generation = 0 so that it
 * won't look like a root page. Optionally, malloc / free can be used
 * for testing a fallback algorithm that works with cheneygc.
 * The fallback is succeptible to deadlock with multiple threads
 * and therefore not suitable for production use.
 * (if a foreign call entails malloc, and malloc potentially acquires a
 * mutex, and the Lisp thread making the foreign call is stopped for GC,
 * then GC can't proceed)
 *
 * Logically this code is common to both GCs, but it requires some
 * knowledge of gencgc's structures. You might think it should be split
 * into two wholly separate implementation files, but the single file
 * facilitates exercising the malloc/free-based implementation
 * with either GC.
 */

#define PRIVATE_CONS_DEBUG 0

#ifdef LISP_FEATURE_CHENEYGC

static struct cons* private_cons_chain;
static int private_cons_n_avail;

#define CHUNKSIZE 4096
static struct cons* private_cons_impl()
{
    if (!private_cons_n_avail) {
        struct cons* new = malloc(CHUNKSIZE);
        private_cons_n_avail = (CHUNKSIZE / sizeof (struct cons)) - 1;
        /* Treating 'new' as an array of 'private_cons_n_avail' conses,
         * we allocate backwards toward the 0th element.
         * Element 0 itself holds the list of chunks to subsequently free. */
        new->car = 0; // unused
        new->cdr = (lispobj)private_cons_chain;
        private_cons_chain = new;
        if (PRIVATE_CONS_DEBUG)
            fprintf(stderr, "%d conses @ %p\n", (1+private_cons_n_avail), new);
    }
    return private_cons_chain + private_cons_n_avail--;
}

static void release_pages_impl()
{
    struct cons* list = private_cons_chain, *next;
    for ( ; list ; list = next ) {
        if (PRIVATE_CONS_DEBUG) fprintf(stderr, "Freeing %p\n", list);
        next = (struct cons*)list->cdr;
        free(list);
    }
    private_cons_chain = NULL;
    private_cons_n_avail = 0;
}

#else

static page_index_t private_cons_page_chain = -1;

static struct cons* private_cons_impl()
{
    page_index_t page = private_cons_page_chain;
    page_bytes_t bytes_used;
    struct cons* cons;

    if (page >= 0 && (bytes_used = page_bytes_used(page)) < GENCGC_CARD_BYTES) {
        cons = (struct cons*)(page_address(page) + bytes_used);
    } else {
        page = alloc_start_page(UNBOXED_PAGE_FLAG, 0);
        page_index_t last_page __attribute__((unused)) =
            gc_find_freeish_pages(&page, GENCGC_CARD_BYTES,
                                  SINGLE_OBJECT_FLAG | UNBOXED_PAGE_FLAG, 0);
        // See question about last_page in gc_alloc_large
        set_alloc_start_page(UNBOXED_PAGE_FLAG, 0, page);

        struct cons* page_header = (struct cons*)page_address(page);
        if (PRIVATE_CONS_DEBUG)
            fprintf(stderr, "GC-private page @ %p\n", page_header);
        gc_assert(last_page == page);
        gc_assert(!page_table[page].write_protected);
        page_table[page].gen = 0;
        page_table[page].type = UNBOXED_PAGE_FLAG;
        zero_dirty_pages(page, page);
        page_index_t tail = private_cons_page_chain;
        page_header->car = 0; // unused
        page_header->cdr = (lispobj)(tail >= 0 ? page_address(tail) : 0);
        private_cons_page_chain = page;
        bytes_used = 2*N_WORD_BYTES; // one cons (so far)
        cons = page_header + 1;
    }
    set_page_bytes_used(page, bytes_used + 2*N_WORD_BYTES);
    return cons;
}

static void release_pages_impl()
{
    struct cons* list;
    if (private_cons_page_chain >= 0) {
        for (list = (struct cons*)page_address(private_cons_page_chain) ;
             list ;
             list = (struct cons*)list->cdr) {
            page_index_t index = find_page_index(list);
            if (PRIVATE_CONS_DEBUG)
                fprintf(stderr, "Freeing GC-private page @ %p (index %ld)\n",
                        list, (long)index);
            set_page_bytes_used(index, 0);
            reset_page_flags(index);
        }
        private_cons_page_chain = -1;
    }
}

#endif

static struct cons* private_cons_recycle_list;

uword_t gc_private_cons(uword_t car, uword_t cdr)
{
    struct cons* cons = private_cons_recycle_list;
    if (cons)
        private_cons_recycle_list = (struct cons*)cons->cdr;
    else
        cons = private_cons_impl();
    cons->car = car;
    cons->cdr = cdr;
    return (uword_t)cons;
}

/* Push all the conses in 'list' onto the recycle list. */
void gc_private_free(struct cons* list)
{
    struct cons* head = list;
    while (list->cdr)
        list = (struct cons*)list->cdr;
    list->cdr = (lispobj)private_cons_recycle_list;
    private_cons_recycle_list = head;
}

/* Give back all the memory used by private cons cells
 * to either the GC allocator or the malloc implementation. */
void gc_dispose_private_pages()
{
    private_cons_recycle_list = 0;
    release_pages_impl();
}
