/*
 * %CopyrightBegin%
 * 
 * Copyright Ericsson AB 2004-2009. All Rights Reserved.
 * 
 * The contents of this file are subject to the Erlang Public License,
 * Version 1.1, (the "License"); you may not use this file except in
 * compliance with the License. You should have received a copy of the
 * Erlang Public License along with this software. If not, it can be
 * retrieved online at http://www.erlang.org/.
 * 
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
 * the License for the specific language governing rights and limitations
 * under the License.
 * 
 * %CopyrightEnd%
 */

#ifndef __ERL_NMGC_H__
#define __ERL_NMGC_H__

#ifdef INCREMENTAL
#include <stddef.h>      /* offsetof() */
#include "erl_process.h"

#define INC_FULLPAGE (INC_PAGESIZE + offsetof(INC_Page,start) / sizeof(void*))

#define BOXED_NEED(PTR,HDR)                                             \
  (((HDR) & _HEADER_SUBTAG_MASK) == SUB_BINARY_SUBTAG ?                 \
    header_arity(HDR) + 2 :                                             \
   ((HDR) & _HEADER_SUBTAG_MASK) == FUN_SUBTAG ?                        \
    header_arity(HDR) + ((ErlFunThing*)(PTR))->num_free + 2 :           \
   header_arity(HDR) + 1)


#define INC_DECREASE_WORK(n) inc_words_to_go -= (n);

#define INC_COPY_CONS(FROM,TO,PTR)					\
do {									\
    TO[0] = FROM[0];							\
    TO[1] = FROM[1];							\
    INC_MARK_FORWARD(FROM,TO);						\
    *(PTR) = make_list(TO);						\
    INC_DECREASE_WORK(2);						\
    (TO) += 2;								\
} while(0)

#define INC_COPY_BOXED(FROM,TO,PTR)					\
do {									\
    Sint nelts;								\
    Eterm hdr = *(FROM);						\
									\
    ASSERT(is_header(hdr));						\
    INC_MARK_FORWARD(FROM,TO);						\
    *(PTR) = make_boxed(TO);						\
    *(TO)++ = *(FROM)++;       						\
    nelts = header_arity(hdr);						\
    switch ((hdr) & _HEADER_SUBTAG_MASK) {				\
    case SUB_BINARY_SUBTAG: nelts++; break;				\
    case FUN_SUBTAG: nelts+=((ErlFunThing*)(FROM-1))->num_free+1; break;\
    }									\
    INC_DECREASE_WORK(nelts + 1);					\
    while (nelts--)							\
	*(TO)++ = *(FROM)++;						\
} while(0)


/* Things copied to the old generation are not marked in the blackmap. 
 * This is ok since the page they are copied to (aging) is not part of
 * the sweep.
 */
#define COPYMARK_CONS(FROM,TO,PTR,LIMIT)                                \
do {							                \
    if (ptr_within(FROM,inc_fromspc,inc_fromend)) {                     \
        if (INC_IS_FORWARDED(FROM)) {                                   \
            *PTR = make_list(INC_FORWARD_VALUE(FROM));                  \
        } else if (TO + 2 <= LIMIT) {                                   \
            INC_STORE(gray,TO,2);                                       \
            INC_COPY_CONS(FROM,TO,PTR);                                 \
        } else {                                                        \
            Eterm *hp = erts_inc_alloc(2);                              \
            INC_STORE(gray,hp,2);                                       \
            INC_COPY_CONS(FROM,hp,PTR);                                 \
        }                                                               \
    } else if (ptr_within(FROM,global_old_heap,global_old_hend) &&     	\
               (blackmap[FROM - global_old_heap] == 0)) {               \
        blackmap[FROM - global_old_heap] = 2;                           \
        INC_DECREASE_WORK(2);                                           \
        INC_STORE(gray,FROM,2);                                         \
    }                                                                   \
} while(0)

#define COPYMARK_BOXED(FROM,TO,PTR,LIMIT)                               \
do {							                \
    if (ptr_within(FROM,inc_fromspc,inc_fromend)) {                     \
        int size = BOXED_NEED(FROM,*FROM);                              \
        if (INC_IS_FORWARDED(FROM)) {                                   \
            *PTR = make_boxed(INC_FORWARD_VALUE(FROM));                 \
        } else if (TO + size <= LIMIT) {                                \
            INC_STORE(gray,TO,size);                                    \
            INC_COPY_BOXED(FROM,TO,PTR);                                \
        } else {                                                        \
            Eterm *hp = erts_inc_alloc(size);                           \
            INC_STORE(gray,hp,size);                                    \
            INC_COPY_BOXED(FROM,hp,PTR);                                \
        }                                                               \
    } else if (ptr_within(FROM,global_old_heap,global_old_hend) &&     	\
               (blackmap[FROM - global_old_heap] == 0)) {               \
        int size = BOXED_NEED(FROM,*FROM);                              \
        if (size > 254) {                                               \
            blackmap[FROM - global_old_heap] = 255;                     \
            *(int*)((long)(&blackmap[FROM -                             \
                                     global_old_heap] + 4) & ~3) = size; \
        } else                                                          \
            blackmap[FROM - global_old_heap] = size;                    \
        INC_DECREASE_WORK(size);                                        \
        INC_STORE(gray,FROM,size);                                      \
    }                                                                   \
} while(0)

#define INC_MARK_FORWARD(ptr,dst) fwdptrs[(ptr) - inc_fromspc] = (dst);
#define INC_IS_FORWARDED(ptr) (fwdptrs[(ptr) - inc_fromspc] != 0)
#define INC_FORWARD_VALUE(ptr) fwdptrs[(ptr) - inc_fromspc]

/* Note for BM_TIMER: Active timer should always be 'system' when IncAlloc
 * is called!
 */
#define IncAlloc(p, sz, objv, nobj)                                     \
    (ASSERT_EXPR((sz) >= 0),                                            \
     (((inc_alloc_limit - global_htop) <= (sz)) ?                       \
      erts_incremental_gc((p),(sz),(objv),(nobj)) : 0),                 \
     ASSERT_EXPR(global_hend - global_htop > (sz)),                     \
     global_htop += (sz), global_htop - (sz))


/************************************************************************
 * INC_STORAGE, a dynamic circular storage for objects (INC_Object).    *
 * Use INC_STORE to add objects to the storage. The storage can then    *
 * be used either as a queue, using INC_STORAGE_GET to retreive         *
 * values, or as a stack, using INC_STORAGE_POP. It is OK to mix calls  *
 * to GET and POP if that is desired.                                   *
 * An iterator can be declared to traverse the storage without removing *
 * any elements, and INC_STORAGE_STEP will then return each element in  *
 * turn, oldest first.                                                  *
 ***********************************************************************/

/* Declare a new storage; must be in the beginning of a block. Give
 * the storage a name that is used in all later calls to the storage. 
 * If this is an external declaration of the storage, pass the keyword
 * external as the first argument, otherwise leave it empty.
 */
#define INC_STORAGE_DECLARATION(ext,name)                               \
    ext INC_Storage *name##head;                                        \
    ext INC_Storage *name##tail;                                        \
    ext INC_Object *name##free;                                         \
    ext INC_Object *name##last_free;                                    \
    ext int name##size;


/* Initialize the storage. Note that memory allocation is involved -
 * don't forget to erase the storage when you are done.
 */
#define INC_STORAGE_INIT(name) do {                                     \
    name##head = (INC_Storage*)erts_alloc(ERTS_ALC_T_OBJECT_STACK,      \
                                          sizeof(INC_Storage));         \
    name##head->next = name##head;                                      \
    name##head->prev = name##head;                                      \
    name##tail = name##head;                                            \
    name##free = name##head->data;                                      \
    name##last_free = name##free + INC_STORAGE_SIZE - 1;                \
    name##size = 0;                                                     \
} while(0)


/*
#define INC_STORAGE_SWAP(s1,s2) do {                                    \
    INC_Storage *tmphead = s1##head;                                    \
    INC_Storage *tmptail = s1##tail;                                    \
    INC_Object *tmpfree = s1##free;                                     \
    INC_Object *tmplast = s1##last_free;                                \
    int tmpsize = s1##size;                                             \
    s1##head = s2##head;                                                \
    s1##tail = s2##tail;                                                \
    s1##free = s2##free;                                                \
    s1##last_free = s2##last_free;                                      \
    s1##size = s2##size;                                                \
    s2##head = tmphead;                                                 \
    s2##tail = tmptail;                                                 \
    s2##free = tmpfree;                                                 \
    s2##last_free = tmplast;                                            \
    s2##size = tmpsize;                                                 \
} while(0)
*/


/* Return and remove the youngest element - treat the storage as a
 * stack. Always check that there are elements in the queue before
 * using INC_STORAGE_POP!
 */
#define INC_STORAGE_POP(name) (ASSERT_EXPR(name##size != 0),            \
    name##size--,                                                       \
    (--name##free != name##head->data - 1) ?                            \
    name##free : (name##head = name##head->prev,                        \
                  name##free = name##head->data + INC_STORAGE_SIZE - 1))


/* Return and remove the oldest element - treat the storage as a
 * queue. Always check that there are elements in the queue before
 * using INC_STORAGE_GET!
 */
#define INC_STORAGE_GET(name) (ASSERT_EXPR(name##size != 0),            \
    name##size--,                                                       \
    (++name##last_free != name##tail->data + INC_STORAGE_SIZE) ?        \
     name##last_free : (name##tail = name##tail->next,                  \
                        name##last_free = name##tail->data))


/* Advance the head to the next free location. If the storage is full,
 * a new storage is allocated and linked into the list.
 */
#define INC_STORAGE_NEXT(name) do {                                     \
    if (name##free == name##last_free) {                                \
        name##tail = (INC_Storage*)erts_alloc(ERTS_ALC_T_OBJECT_STACK,  \
                                              sizeof(INC_Storage));     \
        memcpy(name##tail->data,name##head->data,                       \
               INC_STORAGE_SIZE * sizeof(INC_Object));                  \
        name##tail->next = name##head->next;                            \
        name##head->next = name##tail;                                  \
        name##tail->prev = name##tail->next->prev;                      \
        name##tail->next->prev = name##tail;                            \
        name##last_free = ((void*)name##tail +                          \
                         ((void*)name##last_free - (void*)name##head)); \
    }                                                                   \
    name##free++;                                                       \
    name##size++;                                                       \
    if (name##free == name##head->data + INC_STORAGE_SIZE) {            \
        name##head = name##head->next;                                  \
        name##free = name##head->data;                                  \
    }                                                                   \
} while(0)


/* The head of this storage is the next free location. This is where
 * the next element will be stored.
 */
#define INC_STORAGE_HEAD(name) (name##free)


/* Return the top - the youngest element in the storage. */
/* #define INC_STORAGE_TOP(name) (name##free - 1 with some magic..) */


/* True if the storage is empty, false otherwise */
#define INC_STORAGE_EMPTY(name) (name##size == 0)


/* Store a new element in the head of the storage and advance the head
 * to the next free location.
 */
#define INC_STORE(name,ptr,sz) do {                                      \
    INC_STORAGE_HEAD(name)->this = ptr;                                  \
    INC_STORAGE_HEAD(name)->size = sz;                                   \
    INC_STORAGE_NEXT(name);                                              \
} while(0)


/* An iterator. Use it together with INC_STORAGE_STEP to browse throuh
 * the storage. Please note that it is not possible to remove an entry
 * in the middle of the storage, use GET or POP to remove enties.
 */
#define INC_STORAGE_ITERATOR(name)                                      \
    INC_Storage *name##iterator_head = name##tail;                      \
    INC_Object *name##iterator_current = name##last_free;               \
    int name##iterator_left = name##size;


/* Return the next element in the storage (sorted by age, oldest
 * first) or NULL if the storage is empty or the last element has been
 * returned already.
 */
#define INC_STORAGE_STEP(name) (name##iterator_left == 0 ? NULL :       \
    (name##iterator_left--,                                             \
     (++name##iterator_current != name##iterator_head->data +           \
      INC_STORAGE_SIZE) ? name##iterator_current :                      \
     (name##iterator_head = name##iterator_head->next,                  \
      name##iterator_current = name##iterator_head->data)))


/* Erase the storage. */
#define INC_STORAGE_ERASE(name)do {                             \
    name##head->prev->next = NULL;                              \
    while (name##head != NULL) {                                \
        name##tail = name##head;                                \
        name##head = name##head->next;                          \
        erts_free(ERTS_ALC_T_OBJECT_STACK,(void*)name##tail);   \
    }                                                           \
    name##tail = NULL;                                          \
    name##free = NULL;                                          \
    name##last_free = NULL;                                     \
    name##size = 0;                                             \
} while(0)

/*
 * Structures used by the non-moving memory manager
 */

typedef struct
{
  Eterm *this;
  unsigned long size;
} INC_Object;

typedef struct inc_storage {
  struct inc_storage *next;
  struct inc_storage *prev;
  INC_Object data[INC_STORAGE_SIZE];
} INC_Storage;

typedef struct inc_mem_block
{
  unsigned long size;
  struct inc_mem_block *prev;
  struct inc_mem_block *next;
} INC_MemBlock;

typedef struct inc_page
{
  struct inc_page *next;
  Eterm start[1]; /* Has to be last in struct, this is where the data start */
} INC_Page;


/*
 * Heap pointers for the non-moving memory area.
 */
extern INC_Page *inc_used_mem;
extern INC_MemBlock *inc_free_list;
extern unsigned char *blackmap;

extern Eterm **fwdptrs;
extern Eterm *inc_fromspc;
extern Eterm *inc_fromend;
extern Process *inc_active_proc;
extern Process *inc_active_last;
extern Eterm *inc_alloc_limit;
extern int   inc_words_to_go;

INC_STORAGE_DECLARATION(extern,gray);
INC_STORAGE_DECLARATION(extern,root);

void erts_init_incgc(void);
void erts_cleanup_incgc(void);
void erts_incremental_gc(Process *p, int sz, Eterm* objv, int nobj);
Eterm *erts_inc_alloc(int need);

#else
#  define INC_STORE(lst,ptr,sz)
#  define INC_MARK_FORWARD(ptr)
#  define INC_IS_FORWARDED(ptr)
#  define INC_FORWARD_VALUE(ptr)
#endif /* INCREMENTAL */

#endif /* _ERL_NMGC_H_ */