/*
 * %CopyrightBegin%
 *
 * Copyright Ericsson AB 2012-2023. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * %CopyrightEnd%
 */

/*
 * Description:	Process/Port table implementation.
 *
 * Author: 	Rickard Green
 */

#ifdef HAVE_CONFIG_H
#  include "config.h"
#endif
#include "global.h"
#define ERTS_PTAB_WANT_BIF_IMPL__
#define ERTS_PTAB_WANT_DEBUG_FUNCS__
#include "erl_ptab.h"
#include "erl_binary.h"

typedef struct ErtsPTabListBifData_ ErtsPTabListBifData;

#define ERTS_PTAB_NEW_MAX_RESERVE_FAIL 1000

#define ERTS_PTAB_LIST_BIF_TAB_INSPECT_INDICES_PER_RED 25
#define ERTS_PTAB_LIST_BIF_TAB_CHUNK_SIZE 1000
#define ERTS_PTAB_LIST_BIF_MIN_START_REDS				\
 (ERTS_PTAB_LIST_BIF_TAB_CHUNK_SIZE					\
  / ERTS_PTAB_LIST_BIF_TAB_INSPECT_INDICES_PER_RED)

#define ERTS_PTAB_LIST_BIF_TAB_FREE_DELETED_REDS 1

#define ERTS_PTAB_LIST_BIF_INSPECT_DELETED_PER_RED 10

#define ERTS_PTAB_LIST_INSPECT_DELETED_MAX_REDS				\
 (ERTS_PTAB_LIST_BIF_TAB_CHUNK_SIZE					\
  / ERTS_PTAB_LIST_BIF_TAB_INSPECT_INDICES_PER_RED)
 

#define ERTS_PTAB_LIST_BIF_BUILD_RESULT_CONSES_PER_RED 75

#define ERTS_PTAB_LIST_DBG_DO_TRACE 0

#ifdef DEBUG
#  define ERTS_PTAB_LIST_BIF_DEBUGLEVEL 100
#else
#  define ERTS_PTAB_LIST_BIF_DEBUGLEVEL 0
#endif

#define ERTS_PTAB_LIST_DBGLVL_CHK_HALLOC 1
#define ERTS_PTAB_LIST_DBGLVL_CHK_FOUND_PIDS 5
#define ERTS_PTAB_LIST_DBGLVL_CHK_PIDS 10
#define ERTS_PTAB_LIST_DBGLVL_CHK_DEL_LIST 20
#define ERTS_PTAB_LIST_DBGLVL_CHK_RESLIST 20

#if ERTS_PTAB_LIST_BIF_DEBUGLEVEL == 0
#  define ERTS_PTAB_LIST_ASSERT(EXP)
#else
#  define ERTS_PTAB_LIST_ASSERT(EXP)					\
    ((void) ((EXP)							\
	     ? 1							\
	     : (debug_ptab_list_assert_error(#EXP,			\
					     __FILE__,			\
					     __LINE__,			\
					     __func__),			\
		0)))
#endif


#if ERTS_PTAB_LIST_BIF_DEBUGLEVEL >=  ERTS_PTAB_LIST_DBGLVL_CHK_HALLOC
#  define ERTS_PTAB_LIST_DBG_SAVE_HEAP_ALLOC(PTLBDP, HP, SZ)		\
do {									\
    ERTS_PTAB_LIST_ASSERT(!(PTLBDP)->debug.heap);			\
    ERTS_PTAB_LIST_ASSERT(!(PTLBDP)->debug.heap_size);			\
    (PTLBDP)->debug.heap = (HP);					\
    (PTLBDP)->debug.heap_size = (SZ);					\
} while (0)
#  define ERTS_PTAB_LIST_DBG_VERIFY_HEAP_ALLOC_USED(PTLBDP, HP)		\
do {									\
    ERTS_PTAB_LIST_ASSERT((PTLBDP)->debug.heap);			\
    ERTS_PTAB_LIST_ASSERT((PTLBDP)->debug.heap_size);			\
    ERTS_PTAB_LIST_ASSERT(((PTLBDP)->debug.heap				\
			   + (PTLBDP)->debug.heap_size)			\
			  == (HP));					\
    (PTLBDP)->debug.heap = NULL;					\
    (PTLBDP)->debug.heap_size = 0;					\
} while (0)
#  define ERTS_PTAB_LIST_DBG_HEAP_ALLOC_INIT(PTLBDP)			\
do {									\
    (PTLBDP)->debug.heap = NULL;					\
    (PTLBDP)->debug.heap_size = 0;					\
} while (0)
#else
#  define ERTS_PTAB_LIST_DBG_SAVE_HEAP_ALLOC(PTLBDP, HP, SZ)
#  define ERTS_PTAB_LIST_DBG_VERIFY_HEAP_ALLOC_USED(PTLBDP, HP)
#  define ERTS_PTAB_LIST_DBG_HEAP_ALLOC_INIT(PTLBDP)
#endif

#if ERTS_PTAB_LIST_BIF_DEBUGLEVEL >= ERTS_PTAB_LIST_DBGLVL_CHK_RESLIST
#  define ERTS_PTAB_LIST_DBG_CHK_RESLIST(R)				\
    debug_ptab_list_check_res_list((R))
#else
#  define ERTS_PTAB_LIST_DBG_CHK_RESLIST(R)
#endif

#if ERTS_PTAB_LIST_BIF_DEBUGLEVEL >= ERTS_PTAB_LIST_DBGLVL_CHK_PIDS
#  define ERTS_PTAB_LIST_DBG_SAVE_PIDS(PTLBDP)				\
    debug_ptab_list_save_all_pids((PTLBDP))
#  define ERTS_PTAB_LIST_DBG_VERIFY_PIDS(PTLBDP)			\
do {									\
    if (!(PTLBDP)->debug.correct_pids_verified)				\
	debug_ptab_list_verify_all_pids((PTLBDP));			\
} while (0)
#  define ERTS_PTAB_LIST_DBG_CLEANUP_CHK_PIDS(PTLBDP)			\
do {									\
    if ((PTLBDP)->debug.correct_pids) {					\
	erts_free(ERTS_ALC_T_PTAB_LIST_PIDS,				\
		  (PTLBDP)->debug.correct_pids);			\
	(PTLBDP)->debug.correct_pids = NULL;				\
    }									\
} while(0)
#  define ERTS_PTAB_LIST_DBG_CHK_PIDS_INIT(PTLBDP)			\
do {									\
    (PTLBDP)->debug.correct_pids_verified = 0;				\
    (PTLBDP)->debug.correct_pids = NULL;				\
} while (0)
#else
#  define ERTS_PTAB_LIST_DBG_SAVE_PIDS(PTLBDP)
#  define ERTS_PTAB_LIST_DBG_VERIFY_PIDS(PTLBDP)
#  define ERTS_PTAB_LIST_DBG_CLEANUP_CHK_PIDS(PTLBDP)
#  define ERTS_PTAB_LIST_DBG_CHK_PIDS_INIT(PTLBDP)
#endif

#if ERTS_PTAB_LIST_BIF_DEBUGLEVEL >= ERTS_PTAB_LIST_DBGLVL_CHK_FOUND_PIDS
#  define ERTS_PTAB_LIST_DBG_CHK_PID_FOUND(PTLBDP, PID, IC)		\
  debug_ptab_list_check_found_pid((PTLBDP), (PID), (IC), 1)
#  define ERTS_PTAB_LIST_DBG_CHK_PID_NOT_FOUND(PTLBDP, PID, IC)		\
  debug_ptab_list_check_found_pid((PTLBDP), (PID), (IC), 0)
#else
#  define ERTS_PTAB_LIST_DBG_CHK_PID_FOUND(PTLBDP, PID, IC)
#  define ERTS_PTAB_LIST_DBG_CHK_PID_NOT_FOUND(PTLBDP, PID, IC)
#endif

#if ERTS_PTAB_LIST_BIF_DEBUGLEVEL >= ERTS_PTAB_LIST_DBGLVL_CHK_DEL_LIST
#  define ERTS_PTAB_LIST_DBG_CHK_DEL_LIST(PTab)				\
    debug_ptab_list_check_del_list((PTab))
#  define ERTS_PTAB_LIST_DBG_CHK_FREELIST(PTab, FL)			\
    debug_ptab_list_check_del_free_list((PTab), (FL))
#else
#  define ERTS_PTAB_LIST_DBG_CHK_DEL_LIST(PTab)
#  define ERTS_PTAB_LIST_DBG_CHK_FREELIST(PTab, FL)
#endif

#if ERTS_PTAB_LIST_BIF_DEBUGLEVEL == 0
#if ERTS_PTAB_LIST_DBG_DO_TRACE
#    define ERTS_PTAB_LIST_DBG_INIT(P, PTLBDP)				\
    (PTLBDP)->debug.caller = (P)->common.id
#  else
#    define ERTS_PTAB_LIST_DBG_INIT(P, PTLBDP)
#  endif
#  define ERTS_PTAB_LIST_DBG_CLEANUP(PTLBDP)
#else
#  define ERTS_PTAB_LIST_DBG_INIT(P, PTLBDP)				\
do {									\
    (PTLBDP)->debug.caller = (P)->common.id;				\
    ERTS_PTAB_LIST_DBG_HEAP_ALLOC_INIT((PTLBDP));			\
    ERTS_PTAB_LIST_DBG_CHK_PIDS_INIT((PTLBDP));				\
} while (0)
#  define ERTS_PTAB_LIST_DBG_CLEANUP(PTLBDP)				\
do {									\
    ERTS_PTAB_LIST_DBG_CLEANUP_CHK_PIDS((PTLBDP));			\
} while (0)
#endif

#if ERTS_PTAB_LIST_DBG_DO_TRACE
#  define ERTS_PTAB_LIST_DBG_TRACE(PID, WHAT)				\
     erts_fprintf(stderr, "%T %s:%d:%s(): %s\n",			\
		  (PID), __FILE__, __LINE__, __func__, #WHAT)
#else
#  define ERTS_PTAB_LIST_DBG_TRACE(PID, WHAT)
#endif


#if ERTS_PTAB_LIST_BIF_DEBUGLEVEL != 0
static void debug_ptab_list_assert_error(char* expr,
					 const char* file,
					 int line,
					 const char *func);
#endif
#if ERTS_PTAB_LIST_BIF_DEBUGLEVEL >= ERTS_PTAB_LIST_DBGLVL_CHK_RESLIST
static void debug_ptab_list_check_res_list(Eterm list);
#endif
#if ERTS_PTAB_LIST_BIF_DEBUGLEVEL >= ERTS_PTAB_LIST_DBGLVL_CHK_PIDS
static void debug_ptab_list_save_all_pids(ErtsPTabListBifData *ptlbdp);
static void debug_ptab_list_verify_all_pids(ErtsPTabListBifData *ptlbdp);
#endif
#if ERTS_PTAB_LIST_BIF_DEBUGLEVEL >= ERTS_PTAB_LIST_DBGLVL_CHK_FOUND_PIDS
static void debug_ptab_list_check_found_pid(ErtsPTabListBifData *ptlbdp,
					    Eterm pid,
					    Uint64 ic,
					    int pid_should_be_found);
#endif
#if ERTS_PTAB_LIST_BIF_DEBUGLEVEL >= ERTS_PTAB_LIST_DBGLVL_CHK_DEL_LIST
static void debug_ptab_list_check_del_list(ErtsPTab *ptab);
static void debug_ptab_list_check_del_free_list(ErtsPTab *ptab,
						ErtsPTabDeletedElement *ptdep);
#endif

struct ErtsPTabDeletedElement_ {
    ErtsPTabDeletedElement *next;
    ErtsPTabDeletedElement *prev;
    int ix;
    union {
	struct {
	    Eterm id;
	    Uint64 inserted;
	    Uint64 deleted;
	} element;
	struct {
	    Uint64 interval;
	} bif_invocation;
    } u;
};

static Export ptab_list_continue_export;

typedef struct {
    Uint64 interval;
} ErtsPTabListBifChunkInfo;

typedef enum {
    INITIALIZING,
    INSPECTING_TABLE,
    INSPECTING_DELETED,
    BUILDING_RESULT,
    RETURN_RESULT
} ErtsPTabListBifState;

struct ErtsPTabListBifData_ {
    ErtsPTab *ptab;
    ErtsPTabListBifState state;
    Eterm caller;
    ErtsPTabListBifChunkInfo *chunk;
    int tix;
    int pid_ix;
    int pid_sz;
    Eterm *pid;
    ErtsPTabDeletedElement *bif_invocation; /* Only used when > 1 chunk */

#if ERTS_PTAB_LIST_BIF_DEBUGLEVEL != 0 || ERTS_PTAB_LIST_DBG_DO_TRACE
    struct {
	Eterm caller;
#if ERTS_PTAB_LIST_BIF_DEBUGLEVEL >= ERTS_PTAB_LIST_DBGLVL_CHK_FOUND_PIDS
	Uint64 *pid_started;
#endif
#if ERTS_PTAB_LIST_BIF_DEBUGLEVEL >= ERTS_PTAB_LIST_DBGLVL_CHK_HALLOC
	Eterm *heap;
	Uint heap_size;
#endif
#if ERTS_PTAB_LIST_BIF_DEBUGLEVEL >= ERTS_PTAB_LIST_DBGLVL_CHK_PIDS
	int correct_pids_verified;
	Eterm *correct_pids;
#endif
    } debug;
#endif

};

static ERTS_INLINE void
last_data_init_nob(ErtsPTab *ptab, Uint64 val)
{
    erts_atomic64_init_nob(&ptab->vola.tile.last_data, (erts_aint64_t) val);
}

static ERTS_INLINE void
last_data_set_relb(ErtsPTab *ptab, Uint64 val)
{
    erts_atomic64_set_relb(&ptab->vola.tile.last_data, (erts_aint64_t) val);
}

static ERTS_INLINE Uint64
last_data_read_nob(ErtsPTab *ptab)
{
    return (Uint64) erts_atomic64_read_nob(&ptab->vola.tile.last_data);
}

static ERTS_INLINE Uint64
last_data_read_acqb(ErtsPTab *ptab)
{
    return (Uint64) erts_atomic64_read_acqb(&ptab->vola.tile.last_data);
}

static ERTS_INLINE Uint64
last_data_cmpxchg_relb(ErtsPTab *ptab, Uint64 new, Uint64 exp)
{
    return (Uint64) erts_atomic64_cmpxchg_relb(&ptab->vola.tile.last_data,
						   (erts_aint64_t) new,
						   (erts_aint64_t) exp);
}

static ERTS_INLINE int
last_data_cmp(Uint64 ld1, Uint64 ld2)
{
    Uint64 ld1_wrap;

    if (ld1 == ld2)
	return 0;

    ld1_wrap = ld1 + (((Uint64) 1) << 63);

    if (ld1 < ld1_wrap)
	return (ld1 < ld2 && ld2 < ld1_wrap) ? -1 : 1;
    else
	return (ld1_wrap <= ld2 && ld2 < ld1) ? 1 : -1;
}

#define ERTS_PTAB_LastData2EtermData(LD) \
    ((Eterm) ((LD) & ~(~((Uint64) 0) << ERTS_PTAB_ID_DATA_SIZE)))

static ERTS_INLINE Uint32
ix_to_free_id_data_ix(ErtsPTab *ptab, Uint32 ix)
{
    Uint32 dix;

    dix = ((ix & ptab->r.o.dix_cl_mask) << ptab->r.o.dix_cl_shift);
    dix += ((ix >> ptab->r.o.dix_cli_shift) & ptab->r.o.dix_cli_mask);
    ASSERT(0 <= dix && dix < ptab->r.o.max);
    return dix;
}

UWord
erts_ptab_mem_size(ErtsPTab *ptab)
{
    UWord size = ptab->r.o.max*sizeof(erts_atomic_t);
    if (ptab->r.o.free_id_data)
	size += ptab->r.o.max*sizeof(erts_atomic32_t);
    return size;
}


void
erts_ptab_init_table(ErtsPTab *ptab,
		     ErtsAlcType_t atype,
		     void (*release_element)(void *),
		     ErtsPTabElementCommon *invalid_element,
		     int size,
		     UWord element_size,
		     char *name,
		     int legacy,
		     int atomic_refc)
{
    size_t tab_sz, alloc_sz;
    Uint32 bits, cl, cli, ix, ix_per_cache_line, tab_cache_lines; 
    char *tab_end;
    erts_atomic_t *tab_entry;
    erts_rwmtx_opt_t rwmtx_opts = ERTS_RWMTX_OPT_DEFAULT_INITER;
    rwmtx_opts.type = ERTS_RWMTX_TYPE_EXTREMELY_FREQUENT_READ;
    rwmtx_opts.lived = ERTS_RWMTX_LONG_LIVED;

    erts_rwmtx_init_opt(&ptab->list.data.rwmtx, &rwmtx_opts, name, NIL,
        ERTS_LOCK_FLAGS_PROPERTY_STATIC | ERTS_LOCK_FLAGS_CATEGORY_GENERIC);
    erts_atomic32_init_nob(&ptab->vola.tile.count, 0);
    last_data_init_nob(ptab, ~((Uint64) 0));

    /* A size that is a power of 2 is to prefer performance wise */
    bits = erts_fit_in_bits_int32(size-1);
    size = 1 << bits;
    if (size > ERTS_PTAB_MAX_SIZE) {
	size = ERTS_PTAB_MAX_SIZE;
	bits = erts_fit_in_bits_int32((Sint32) size - 1);
    }

    ptab->r.o.element_size = element_size;
    ptab->r.o.max = size;

    tab_sz = ERTS_ALC_CACHE_LINE_ALIGN_SIZE(size*sizeof(erts_atomic_t));
    alloc_sz = tab_sz;
    if (!legacy)
	alloc_sz += ERTS_ALC_CACHE_LINE_ALIGN_SIZE(size*sizeof(erts_atomic_t));
    ptab->r.o.tab = erts_alloc_permanent_cache_aligned(atype, alloc_sz);
    tab_end = ((char *) ptab->r.o.tab) + tab_sz;
    tab_entry = ptab->r.o.tab;
    while (tab_end > ((char *) tab_entry)) {
	erts_atomic_init_nob(tab_entry, ERTS_AINT_NULL);
	tab_entry++;
    }

    tab_cache_lines = tab_sz/ERTS_CACHE_LINE_SIZE;
    ix_per_cache_line = (ERTS_CACHE_LINE_SIZE/sizeof(erts_atomic_t));
    ASSERT((ptab->r.o.max & (ptab->r.o.max - 1)) == 0); /* power of 2 */
    ASSERT((ix_per_cache_line & (ix_per_cache_line - 1)) == 0); /* power of 2 */
    ASSERT((tab_cache_lines & (tab_cache_lines - 1)) == 0); /* power of 2 */

    ptab->r.o.pix_mask = (1 << bits) - 1;
    ptab->r.o.pix_cl_mask = tab_cache_lines-1;
    ptab->r.o.pix_cl_shift = erts_fit_in_bits_int32(ix_per_cache_line-1);
    ptab->r.o.pix_cli_shift = erts_fit_in_bits_int32(ptab->r.o.pix_cl_mask);
    ptab->r.o.pix_cli_mask = (1 << (bits - ptab->r.o.pix_cli_shift)) - 1;

    ASSERT(ptab->r.o.pix_cl_shift + ptab->r.o.pix_cli_shift == bits);

    ptab->r.o.invalid_element = invalid_element;
    ptab->r.o.invalid_data = erts_ptab_id2data(ptab, invalid_element->id);
    ptab->r.o.release_element = release_element;

    ptab->r.o.atomic_refc = atomic_refc;

    if (legacy) {
	ptab->r.o.free_id_data = NULL;
	ptab->r.o.dix_cl_mask = 0;
	ptab->r.o.dix_cl_shift = 0;
	ptab->r.o.dix_cli_shift = 0;
	ptab->r.o.dix_cli_mask = 0;
    }
    else {

	tab_sz = ERTS_ALC_CACHE_LINE_ALIGN_SIZE(size*sizeof(erts_atomic_t));
	ptab->r.o.free_id_data = (erts_atomic_t *) tab_end;

	tab_cache_lines = tab_sz/ERTS_CACHE_LINE_SIZE;
	ix_per_cache_line = (ERTS_CACHE_LINE_SIZE/sizeof(erts_atomic_t));

	ptab->r.o.dix_cl_mask = tab_cache_lines-1;
	ptab->r.o.dix_cl_shift = erts_fit_in_bits_int32(ix_per_cache_line-1);
	ptab->r.o.dix_cli_shift = erts_fit_in_bits_int32(ptab->r.o.dix_cl_mask);
	ptab->r.o.dix_cli_mask = (1 << (bits - ptab->r.o.dix_cli_shift)) - 1;

	ASSERT((ix_per_cache_line & (ix_per_cache_line - 1)) == 0); /* power of 2 */
	ASSERT((tab_cache_lines & (tab_cache_lines - 1)) == 0); /* power of 2 */

	ASSERT(ptab->r.o.dix_cl_shift + ptab->r.o.dix_cli_shift == bits);

	ix = 0;
	for (cl = 0; cl < tab_cache_lines; cl++) {
	    for (cli = 0; cli < ix_per_cache_line; cli++) {
		erts_atomic_init_nob(&ptab->r.o.free_id_data[ix],
                                     cli*tab_cache_lines+cl);
		ASSERT(erts_atomic_read_nob(&ptab->r.o.free_id_data[ix]) != ptab->r.o.invalid_data);
		ix++;
	    }
	}

	erts_atomic32_init_nob(&ptab->vola.tile.aid_ix, -1);
	erts_atomic32_init_nob(&ptab->vola.tile.fid_ix, -1);

    }

    erts_interval_init(&ptab->list.data.interval);
    ptab->list.data.deleted.start = NULL;
    ptab->list.data.deleted.end = NULL;
    ptab->list.data.chunks = (((ptab->r.o.max - 1)
			       / ERTS_PTAB_LIST_BIF_TAB_CHUNK_SIZE)
			      + 1);

    if (size == ERTS_PTAB_MAX_SIZE) {
	int pix;
	/*
	 * We want a table size of a power of 2 which ERTS_PTAB_MAX_SIZE
	 * is. We only have ERTS_PTAB_MAX_SIZE-1 unique identifiers and
	 * we don't want to shrink the size to ERTS_PTAB_MAX_SIZE/2.
	 *
	 * In order to fix this, we insert a pointer from the table
	 * to the invalid_element, which will be interpreted as a
	 * slot currently being modified. This way we will be able to
	 * have ERTS_PTAB_MAX_SIZE-1 valid elements in the table while
	 * still having a table size of the power of 2.
	 */
	erts_atomic32_inc_nob(&ptab->vola.tile.count);
	pix = erts_ptab_data2pix(ptab, ptab->r.o.invalid_data);
	erts_atomic_set_relb(&ptab->r.o.tab[pix],
				 (erts_aint_t) ptab->r.o.invalid_element);
    }

}

int
erts_ptab_initialized(ErtsPTab *ptab)
{
    return ptab->r.o.tab != NULL;
}

int
erts_ptab_new_element(ErtsPTab *ptab,
		      ErtsPTabElementCommon *ptab_el,
		      void *init_arg,
		      void (*init_ptab_el)(void *, Eterm))
{
    Uint32 pix, ix;
    Uint data;
    erts_aint32_t count;
    erts_aint_t invalid = (erts_aint_t) ptab->r.o.invalid_element;

    erts_ptab_rlock(ptab);

    count = erts_atomic32_inc_read_acqb(&ptab->vola.tile.count);
    if (count > ptab->r.o.max) {
	while (1) {
	    erts_aint32_t act_count;

	    act_count = erts_atomic32_cmpxchg_relb(&ptab->vola.tile.count,
						       count-1,
						       count);
	    if (act_count == count) {
		erts_ptab_runlock(ptab);
		return 0;
	    }
	    count = act_count;
	    if (count <= ptab->r.o.max)
		break;
	}
    }

    ptab_el->u.alive.started_interval
	= erts_current_interval_nob(erts_ptab_interval(ptab));

    if (ptab->r.o.free_id_data) {
	do {
	    ix = (Uint32) erts_atomic32_inc_read_acqb(&ptab->vola.tile.aid_ix);
	    ix = ix_to_free_id_data_ix(ptab, ix);

	    data = erts_atomic_xchg_nob(&ptab->r.o.free_id_data[ix],
                                        (erts_aint_t) ptab->r.o.invalid_data);
	}while ((Eterm)data == ptab->r.o.invalid_data);

	init_ptab_el(init_arg, (Eterm) data);

	if (ptab->r.o.atomic_refc)
	    erts_atomic_init_nob(&ptab_el->refc.atmc, 1);
	else
	    ptab_el->refc.sint = 1;

	pix = erts_ptab_data2pix(ptab, (Eterm) data);

#ifdef DEBUG
	ASSERT(ERTS_AINT_NULL == erts_atomic_xchg_relb(&ptab->r.o.tab[pix],
							   (erts_aint_t) ptab_el));
#else
	erts_atomic_set_relb(&ptab->r.o.tab[pix], (erts_aint_t) ptab_el);
#endif

	erts_ptab_runlock(ptab);

    }
    else {
	int rlocked = ERTS_PTAB_NEW_MAX_RESERVE_FAIL;
	Uint64 ld, exp_ld;
	/* Deprecated legacy algorithm... */

    restart:

	ptab_el->u.alive.started_interval
	    = erts_current_interval_nob(erts_ptab_interval(ptab));

	ld = last_data_read_acqb(ptab);

	/* Reserve slot */
	while (1) {
	    ld++;
	    pix = erts_ptab_data2pix(ptab, ERTS_PTAB_LastData2EtermData(ld));
	    if (erts_atomic_read_nob(&ptab->r.o.tab[pix])
		== ERTS_AINT_NULL) {
		erts_aint_t val;
		val = erts_atomic_cmpxchg_relb(&ptab->r.o.tab[pix],
						   invalid,	
						   ERTS_AINT_NULL);

		if (ERTS_AINT_NULL == val)
		    break;
	    }
	    if (rlocked && --rlocked == 0) {
		erts_ptab_runlock(ptab);
		erts_ptab_rwlock(ptab);
		goto restart;
	    }
	}

	data = ERTS_PTAB_LastData2EtermData(ld);

	if (data == ptab->r.o.invalid_data) {
	    /* Do not use invalid data; fix it... */
	    ld += ptab->r.o.max;
	    ASSERT(pix == erts_ptab_data2pix(ptab,
					     ERTS_PTAB_LastData2EtermData(ld)));
	    data = ERTS_PTAB_LastData2EtermData(ld);
	    ASSERT(data != ptab->r.o.invalid_data);
	}

	exp_ld = last_data_read_nob(ptab);

	/* Move last data forward */
	while (1) {
	    Uint64 act_ld;
	    if (last_data_cmp(ld, exp_ld) < 0)
		break;
	    act_ld = last_data_cmpxchg_relb(ptab, ld, exp_ld);
	    if (act_ld == exp_ld)
		break;
	    exp_ld = act_ld;
	}

	init_ptab_el(init_arg, data);

	if (ptab->r.o.atomic_refc)
	    erts_atomic_init_nob(&ptab_el->refc.atmc, 1);
	else
	    ptab_el->refc.sint = 1;

	/* Move into slot reserved */
#ifdef DEBUG
	ASSERT(invalid == erts_atomic_xchg_relb(&ptab->r.o.tab[pix],
						(erts_aint_t) ptab_el));
#else
	erts_atomic_set_relb(&ptab->r.o.tab[pix], (erts_aint_t) ptab_el);
#endif

	if (rlocked)
	    erts_ptab_runlock(ptab);
	else
	    erts_ptab_rwunlock(ptab);

    }

    return 1;
}

static void
save_deleted_element(ErtsPTab *ptab, ErtsPTabElementCommon *ptab_el)
{
    ErtsPTabDeletedElement *ptdep = erts_alloc(ERTS_ALC_T_PTAB_LIST_DEL,
					       sizeof(ErtsPTabDeletedElement));
    ERTS_PTAB_LIST_ASSERT(ptab->list.data.deleted.start
			  && ptab->list.data.deleted.end);
    ERTS_LC_ASSERT(erts_lc_ptab_is_rwlocked(ptab));

    ERTS_PTAB_LIST_DBG_CHK_DEL_LIST(ptab);

    ptdep->prev = ptab->list.data.deleted.end;
    ptdep->next = NULL;
    ptdep->ix = erts_ptab_id2pix(ptab, ptab_el->id);
    ptdep->u.element.id = ptab_el->id;
    ptdep->u.element.inserted = ptab_el->u.alive.started_interval;
    ptdep->u.element.deleted =
	erts_current_interval_nob(erts_ptab_interval(ptab));

    ptab->list.data.deleted.end->next = ptdep;
    ptab->list.data.deleted.end = ptdep;

    ERTS_PTAB_LIST_DBG_CHK_DEL_LIST(ptab);

    ERTS_PTAB_LIST_ASSERT(ptdep->prev->ix >= 0
			  ? (ptdep->u.element.deleted
			     >= ptdep->prev->u.element.deleted)
			  : (ptdep->u.element.deleted
			     >= ptdep->prev->u.bif_invocation.interval));
}

void
erts_ptab_delete_element(ErtsPTab *ptab,
			 ErtsPTabElementCommon *ptab_el)
{
    int maybe_save;
    Uint32 pix, ix;
    Uint data;

    pix = erts_ptab_id2pix(ptab, ptab_el->id);

    /* *Need* to be an managed thread */
    ERTS_LC_ASSERT(erts_thr_progress_is_managed_thread());

    erts_ptab_rlock(ptab);
    maybe_save = ptab->list.data.deleted.end != NULL;
    if (maybe_save) {
	erts_ptab_runlock(ptab);
	erts_ptab_rwlock(ptab);
    }

    erts_atomic_set_relb(&ptab->r.o.tab[pix], ERTS_AINT_NULL);

    if (ptab->r.o.free_id_data) {
	Uint prev_data;
	/* Next data for this slot... */
	data = erts_ptab_id2data(ptab, ptab_el->id);
	data += ptab->r.o.max;
	data &= ~(~UWORD_CONSTANT(0) << ERTS_PTAB_ID_DATA_SIZE);
	if (data == ptab->r.o.invalid_data) { /* make sure not invalid */
	    data += ptab->r.o.max;
	    data &= ~(~UWORD_CONSTANT(0) << ERTS_PTAB_ID_DATA_SIZE);
	}
	ASSERT(data != ptab->r.o.invalid_data);
	ASSERT(pix == erts_ptab_data2pix(ptab, data));

	do { 
	    ix = (Uint32) erts_atomic32_inc_read_relb(&ptab->vola.tile.fid_ix);
	    ix = ix_to_free_id_data_ix(ptab, ix);
    
	    prev_data = erts_atomic_cmpxchg_nob(&ptab->r.o.free_id_data[ix],
                                                data,
                                                ptab->r.o.invalid_data);
	}while ((Eterm)prev_data != ptab->r.o.invalid_data);
    }

    ASSERT(erts_atomic32_read_nob(&ptab->vola.tile.count) > 0);
    erts_atomic32_dec_relb(&ptab->vola.tile.count);

    if (!maybe_save)
	erts_ptab_runlock(ptab);
    else {
	if (ptab->list.data.deleted.end)
	    save_deleted_element(ptab, ptab_el);
	erts_ptab_rwunlock(ptab);
    }

    if (ptab->r.o.release_element)
	erts_schedule_thr_prgr_later_cleanup_op(ptab->r.o.release_element,
						(void *) ptab_el,
						&ptab_el->u.release,
						ptab->r.o.element_size);
}

/*
 * erts_ptab_list() implements BIFs listing the content of the table,
 * e.g. erlang:processes/0.
 */
static int cleanup_ptab_list_bif_data(Binary *bp);
static int ptab_list_bif_engine(Process *c_p, Eterm *res_accp, Binary *mbp);


BIF_RETTYPE
erts_ptab_list(Process *c_p, ErtsPTab *ptab)
{
    /*
     * A requirement: The list of identifiers returned should be a
     *                consistent snapshot of all elements existing
     *                in the table at some point in time during the
     *                execution of the BIF calling this function.
     *                Since elements might be deleted while the BIF
     *                is executing, we have to keep track of all
     *                deleted elements and add them to the result.
     *                We also ignore elements created after the BIF
     *                has begun executing.
     */
    BIF_RETTYPE ret_val;
    Eterm res_acc = NIL;
    Binary *mbp = erts_create_magic_binary(sizeof(ErtsPTabListBifData),
					   cleanup_ptab_list_bif_data);
    ErtsPTabListBifData *ptlbdp = ERTS_MAGIC_BIN_DATA(mbp);

    ERTS_PTAB_LIST_DBG_TRACE(c_p->common.id, call);
    ptlbdp->ptab = ptab;
    ptlbdp->state = INITIALIZING;
    ERTS_PTAB_LIST_DBG_INIT(c_p, ptlbdp);

    if (ERTS_BIF_REDS_LEFT(c_p) >= ERTS_PTAB_LIST_BIF_MIN_START_REDS
	&& ptab_list_bif_engine(c_p, &res_acc, mbp)) {
	erts_bin_free(mbp);
	ERTS_PTAB_LIST_DBG_CHK_RESLIST(res_acc);
	ERTS_PTAB_LIST_DBG_TRACE(c_p->common.id, return);
	ERTS_BIF_PREP_RET(ret_val, res_acc);
    }
    else {
	Eterm *hp;
	Eterm magic_ref;
	ERTS_PTAB_LIST_DBG_CHK_RESLIST(res_acc);
	hp = HAlloc(c_p, ERTS_MAGIC_REF_THING_SIZE);
	ERTS_PTAB_LIST_DBG_SAVE_HEAP_ALLOC(ptlbdp, hp, ERTS_MAGIC_REF_THING_SIZE);
	magic_ref = erts_mk_magic_ref(&hp, &MSO(c_p), mbp);
	ERTS_PTAB_LIST_DBG_VERIFY_HEAP_ALLOC_USED(ptlbdp, hp);
	ERTS_PTAB_LIST_DBG_TRACE(c_p->common.id, trap);
	ERTS_BIF_PREP_YIELD2(ret_val,
			     &ptab_list_continue_export,
			     c_p,
			     res_acc,
			     magic_ref);
    }
    return ret_val;
}

static int
cleanup_ptab_list_bif_data(Binary *bp)
{
    ErtsPTabListBifData *ptlbdp = ERTS_MAGIC_BIN_DATA(bp);
    ErtsPTab *ptab = ptlbdp->ptab;

    ERTS_PTAB_LIST_DBG_TRACE(ptlbdp->debug.caller, call);

    if (ptlbdp->state != INITIALIZING) {

	if (ptlbdp->chunk) {
	    erts_free(ERTS_ALC_T_PTAB_LIST_CNKI, ptlbdp->chunk);
	    ptlbdp->chunk = NULL;
	}
	if (ptlbdp->pid) {
	    erts_free(ERTS_ALC_T_PTAB_LIST_PIDS, ptlbdp->pid);
	    ptlbdp->pid = NULL;
	}

#if ERTS_PTAB_LIST_BIF_DEBUGLEVEL >= ERTS_PTAB_LIST_DBGLVL_CHK_FOUND_PIDS
	if (ptlbdp->debug.pid_started) {
	    erts_free(ERTS_ALC_T_PTAB_LIST_PIDS, ptlbdp->debug.pid_started);
	    ptlbdp->debug.pid_started = NULL;
	}
#endif

	if (ptlbdp->bif_invocation) {
	    ErtsPTabDeletedElement *ptdep;

	    erts_ptab_rwlock(ptab);

	    ERTS_PTAB_LIST_DBG_TRACE(ptlbdp->debug.caller, deleted_cleanup);

	    ptdep = ptlbdp->bif_invocation;
	    ptlbdp->bif_invocation = NULL;

	    ERTS_PTAB_LIST_DBG_CHK_DEL_LIST(ptab);

	    if (ptdep->prev) {
		/*
		 * Only remove this bif invocation when we
		 * have preceding invocations.
		 */
		ptdep->prev->next = ptdep->next;
		if (ptdep->next)
		    ptdep->next->prev = ptdep->prev;
		else {
		    /*
		     * At the time of writing this branch cannot be
		     * reached. I don't want to remove this code though
		     * since it may be possible to reach this line
		     * in the future if the cleanup order in
		     * erts_do_exit_process() is changed. The ASSERT(0)
		     * is only here to make us aware that the reorder
		     * has happened. /rickard
		     */
		    ASSERT(0);
		    ptab->list.data.deleted.end = ptdep->prev;
		}
		erts_free(ERTS_ALC_T_PTAB_LIST_DEL, ptdep);
	    }
	    else {
		/*
		 * Free all elements until next bif invocation
		 * is found.
		 */
		ERTS_PTAB_LIST_ASSERT(ptab->list.data.deleted.start == ptdep);
		do {
		    ErtsPTabDeletedElement *fptdep = ptdep;
		    ptdep = ptdep->next;
		    erts_free(ERTS_ALC_T_PTAB_LIST_DEL, fptdep);
		} while (ptdep && ptdep->ix >= 0);
		ptab->list.data.deleted.start = ptdep;
		if (ptdep)
		    ptdep->prev = NULL;
		else
		    ptab->list.data.deleted.end = NULL;
	    }

	    ERTS_PTAB_LIST_DBG_CHK_DEL_LIST(ptab);

	    erts_ptab_rwunlock(ptab);

	}
    }

    ERTS_PTAB_LIST_DBG_TRACE(ptlbdp->debug.caller, return);
    ERTS_PTAB_LIST_DBG_CLEANUP(ptlbdp);

    return 1;
}

static int
ptab_list_bif_engine(Process *c_p, Eterm *res_accp, Binary *mbp)
{
    ErtsPTabListBifData *ptlbdp = ERTS_MAGIC_BIN_DATA(mbp);
    ErtsPTab *ptab = ptlbdp->ptab;
    int have_reds;
    int reds;
    int locked = 0;

    do {
	switch (ptlbdp->state) {
	case INITIALIZING:
	    ptlbdp->chunk = erts_alloc(ERTS_ALC_T_PTAB_LIST_CNKI,
				       (sizeof(ErtsPTabListBifChunkInfo)
					* ptab->list.data.chunks));
	    ptlbdp->tix = 0;
	    ptlbdp->pid_ix = 0;

	    erts_ptab_rwlock(ptab);
	    locked = 1;

	    ERTS_PTAB_LIST_DBG_TRACE(c_p->common.id, init);

	    ptlbdp->pid_sz = erts_ptab_count(ptab);
	    ptlbdp->pid = erts_alloc(ERTS_ALC_T_PTAB_LIST_PIDS,
				     sizeof(Eterm)*ptlbdp->pid_sz);

#if ERTS_PTAB_LIST_BIF_DEBUGLEVEL >= ERTS_PTAB_LIST_DBGLVL_CHK_FOUND_PIDS
	    ptlbdp->debug.pid_started
		= erts_alloc(ERTS_ALC_T_PTAB_LIST_PIDS,
			     sizeof(Uint64)*ptlbdp->pid_sz);
#endif

	    ERTS_PTAB_LIST_DBG_SAVE_PIDS(ptlbdp);

	    if (ptab->list.data.chunks == 1)
		ptlbdp->bif_invocation = NULL;
	    else {
		/*
		 * We will have to access the table multiple times
		 * releasing the table lock in between chunks.
		 */
		ptlbdp->bif_invocation
		    = erts_alloc(ERTS_ALC_T_PTAB_LIST_DEL,
				 sizeof(ErtsPTabDeletedElement));
		ptlbdp->bif_invocation->ix = -1;
		ptlbdp->bif_invocation->u.bif_invocation.interval
		    = erts_step_interval_nob(erts_ptab_interval(ptab));
		ERTS_PTAB_LIST_DBG_CHK_DEL_LIST(ptab);

		ptlbdp->bif_invocation->next = NULL;
		if (ptab->list.data.deleted.end) {
		    ptlbdp->bif_invocation->prev = ptab->list.data.deleted.end;
		    ptab->list.data.deleted.end->next = ptlbdp->bif_invocation;
		    ERTS_PTAB_LIST_ASSERT(ptab->list.data.deleted.start);
		}
		else {
		    ptlbdp->bif_invocation->prev = NULL;
		    ptab->list.data.deleted.start = ptlbdp->bif_invocation;
		}
		ptab->list.data.deleted.end = ptlbdp->bif_invocation;

		ERTS_PTAB_LIST_DBG_CHK_DEL_LIST(ptab);

	    }

	    ptlbdp->state = INSPECTING_TABLE;
	    /* Fall through */

	case INSPECTING_TABLE: {
	    int ix = ptlbdp->tix;
	    int indices = ERTS_PTAB_LIST_BIF_TAB_CHUNK_SIZE;
	    int cix = ix / ERTS_PTAB_LIST_BIF_TAB_CHUNK_SIZE;
	    int end_ix = ix + indices;
	    Uint64 *invocation_interval_p;
	    ErtsPTabElementCommon *invalid_element;

	    invocation_interval_p
		= (ptlbdp->bif_invocation
		   ? &ptlbdp->bif_invocation->u.bif_invocation.interval
		   : NULL);

	    ERTS_PTAB_LIST_ASSERT(is_nil(*res_accp));
	    if (!locked) {
		erts_ptab_rwlock(ptab);
		locked = 1;
	    }

	    ERTS_LC_ASSERT(erts_lc_ptab_is_rwlocked(ptab));
	    ERTS_PTAB_LIST_DBG_TRACE(p->common.id, insp_table);

	    if (cix != 0)
		ptlbdp->chunk[cix].interval
		    = erts_step_interval_nob(erts_ptab_interval(ptab));
	    else if (invocation_interval_p)
		ptlbdp->chunk[0].interval = *invocation_interval_p;
	    /* else: interval is irrelevant */

	    if (end_ix >= ptab->r.o.max) {
		ERTS_PTAB_LIST_ASSERT(cix+1 == ptab->list.data.chunks);
		end_ix = ptab->r.o.max;
		indices = end_ix - ix;
		/* What to do when done with this chunk */
		ptlbdp->state = (ptab->list.data.chunks == 1
				 ? BUILDING_RESULT
				 : INSPECTING_DELETED);
	    }

	    invalid_element = ptab->r.o.invalid_element;
	    for (; ix < end_ix; ix++) {
		ErtsPTabElementCommon *el;
		el = (ErtsPTabElementCommon *) erts_ptab_pix2intptr_nob(ptab,
									ix);
		if (el
		    && el != invalid_element
		    && (!invocation_interval_p
			|| el->u.alive.started_interval < *invocation_interval_p)) {
		    ERTS_PTAB_LIST_ASSERT(erts_ptab_is_valid_id(el->id));
		    ptlbdp->pid[ptlbdp->pid_ix] = el->id;

#if ERTS_PTAB_LIST_BIF_DEBUGLEVEL >= ERTS_PTAB_LIST_DBGLVL_CHK_FOUND_PIDS
		    ptlbdp->debug.pid_started[ptlbdp->pid_ix]
			= el->u.alive.started_interval;
#endif

		    ptlbdp->pid_ix++;
		    ERTS_PTAB_LIST_ASSERT(ptlbdp->pid_ix <= ptlbdp->pid_sz);
		}
	    }

	    ptlbdp->tix = end_ix;
	    
	    erts_ptab_rwunlock(ptab);
	    locked = 0;

	    reds = indices/ERTS_PTAB_LIST_BIF_TAB_INSPECT_INDICES_PER_RED;
	    BUMP_REDS(c_p, reds);

	    have_reds = ERTS_BIF_REDS_LEFT(c_p);

	    if (have_reds && ptlbdp->state == INSPECTING_TABLE) {
		ix = ptlbdp->tix;
		indices = ERTS_PTAB_LIST_BIF_TAB_CHUNK_SIZE;
		end_ix = ix + indices;
		if (end_ix > ptab->r.o.max) {
		    end_ix = ptab->r.o.max;
		    indices = end_ix - ix;
		}
		
		reds = indices/ERTS_PTAB_LIST_BIF_TAB_INSPECT_INDICES_PER_RED;

		/* Pretend we have no reds left if we haven't got enough
		   reductions to complete next chunk */
		if (reds > have_reds)
		    have_reds = 0;
	    }

	    break;
	}

	case INSPECTING_DELETED: {
	    int i;
	    int max_reds;
	    int free_deleted = 0;
	    Uint64 invocation_interval;
	    ErtsPTabDeletedElement *ptdep;
	    ErtsPTabDeletedElement *free_list = NULL;

	    ptdep = ptlbdp->bif_invocation;
	    ERTS_PTAB_LIST_ASSERT(ptdep);
	    invocation_interval = ptdep->u.bif_invocation.interval;

	    max_reds = have_reds = ERTS_BIF_REDS_LEFT(c_p);
	    if (max_reds > ERTS_PTAB_LIST_INSPECT_DELETED_MAX_REDS)
		max_reds = ERTS_PTAB_LIST_INSPECT_DELETED_MAX_REDS;

	    reds = 0;
	    erts_ptab_rwlock(ptab);
	    ERTS_PTAB_LIST_DBG_TRACE(p->common.id, insp_term_procs);

	    ERTS_PTAB_LIST_DBG_CHK_DEL_LIST(ptab);

	    if (ptdep->prev)
		ptdep->prev->next = ptdep->next;
	    else {
		ERTS_PTAB_LIST_ASSERT(ptab->list.data.deleted.start == ptdep);
		ptab->list.data.deleted.start = ptdep->next;

		if (ptab->list.data.deleted.start
		    && ptab->list.data.deleted.start->ix >= 0) {
		    free_list = ptab->list.data.deleted.start;
		    free_deleted = 1;
		}
	    }

	    if (ptdep->next)
		ptdep->next->prev = ptdep->prev;
	    else
		ptab->list.data.deleted.end = ptdep->prev;

	    ptdep = ptdep->next;

	    i = 0;
	    while (reds < max_reds && ptdep) {
		if (ptdep->ix < 0) {
		    if (free_deleted) {
			ERTS_PTAB_LIST_ASSERT(free_list);
			ERTS_PTAB_LIST_ASSERT(ptdep->prev);

			ptdep->prev->next = NULL; /* end of free_list */
			ptab->list.data.deleted.start = ptdep;
			ptdep->prev = NULL;
			free_deleted = 0;
		    }
		}
		else {
		    int cix = ptdep->ix/ERTS_PTAB_LIST_BIF_TAB_CHUNK_SIZE;
		    Uint64 chunk_interval = ptlbdp->chunk[cix].interval;
		    Eterm pid = ptdep->u.element.id;
		    ERTS_PTAB_LIST_ASSERT(erts_ptab_is_valid_id(pid));

		    if (ptdep->u.element.inserted < invocation_interval) {
			if (ptdep->u.element.deleted < chunk_interval) {
			    ERTS_PTAB_LIST_DBG_CHK_PID_NOT_FOUND(
				ptlbdp,
				pid,
				ptdep->u.element.inserted);
			    ptlbdp->pid[ptlbdp->pid_ix] = pid;
#if ERTS_PTAB_LIST_BIF_DEBUGLEVEL >= ERTS_PTAB_LIST_DBGLVL_CHK_FOUND_PIDS
			    ptlbdp->debug.pid_started[ptlbdp->pid_ix]
				= ptdep->u.element.inserted;
#endif
			    ptlbdp->pid_ix++;
			    ERTS_PTAB_LIST_ASSERT(ptlbdp->pid_ix
						  <= ptlbdp->pid_sz);
			}
			else {
			    ERTS_PTAB_LIST_DBG_CHK_PID_FOUND(
				ptlbdp,
				pid,
				ptdep->u.element.inserted);
			}
		    }
		    else {
			ERTS_PTAB_LIST_DBG_CHK_PID_NOT_FOUND(
			    ptlbdp,
			    pid,
			    ptdep->u.element.inserted);
		    }

		    i++;
		    if (i == ERTS_PTAB_LIST_BIF_INSPECT_DELETED_PER_RED) {
			reds++;
			i = 0;
		    }
		    if (free_deleted)
			reds += ERTS_PTAB_LIST_BIF_TAB_FREE_DELETED_REDS;
		}
		ptdep = ptdep->next;
	    }

	    if (free_deleted) {
 		ERTS_PTAB_LIST_ASSERT(free_list);
		ptab->list.data.deleted.start = ptdep;
		if (!ptdep)
		    ptab->list.data.deleted.end = NULL;
		else {
		    ERTS_PTAB_LIST_ASSERT(ptdep->prev);
		    ptdep->prev->next = NULL; /* end of free_list */
		    ptdep->prev = NULL;
		}
	    }

	    if (!ptdep) {
		/* Done */
		ERTS_PTAB_LIST_ASSERT(ptlbdp->pid_ix == ptlbdp->pid_sz);
		ptlbdp->state = BUILDING_RESULT;
		ptlbdp->bif_invocation->next = free_list;
		free_list = ptlbdp->bif_invocation;
		ptlbdp->bif_invocation = NULL;
	    }
	    else {
		/* Link in bif_invocation again where we left off */
		ptlbdp->bif_invocation->prev = ptdep->prev;
		ptlbdp->bif_invocation->next = ptdep;
		ptdep->prev = ptlbdp->bif_invocation;
		if (ptlbdp->bif_invocation->prev)
		    ptlbdp->bif_invocation->prev->next = ptlbdp->bif_invocation;
		else {
		    ERTS_PTAB_LIST_ASSERT(ptab->list.data.deleted.start
					  == ptdep);
		    ptab->list.data.deleted.start = ptlbdp->bif_invocation;
		}
	    }

	    ERTS_PTAB_LIST_DBG_CHK_DEL_LIST(ptab);
	    ERTS_PTAB_LIST_DBG_CHK_FREELIST(ptab, free_list);
	    erts_ptab_rwunlock(ptab);

	    /*
	     * We do the actual free of deleted structures now when we
	     * have released the table lock instead of when we encountered
	     * them. This since free() isn't for free and we don't want to
	     * unnecessarily block other schedulers.
	     */
	    while (free_list) {
		ptdep = free_list;
		free_list = ptdep->next;
		erts_free(ERTS_ALC_T_PTAB_LIST_DEL, ptdep);
	    }

	    have_reds -= reds;
	    if (have_reds < 0)	
		have_reds = 0;
	    BUMP_REDS(c_p, reds);
	    break;
	}

	case BUILDING_RESULT: {
	    int conses, ix, min_ix;
	    Eterm *hp;
	    Eterm res = *res_accp;

	    ERTS_PTAB_LIST_DBG_VERIFY_PIDS(ptlbdp);
	    ERTS_PTAB_LIST_DBG_CHK_RESLIST(res);

	    ERTS_PTAB_LIST_DBG_TRACE(p->common.id, begin_build_res);

	    have_reds = ERTS_BIF_REDS_LEFT(c_p);
	    conses = ERTS_PTAB_LIST_BIF_BUILD_RESULT_CONSES_PER_RED*have_reds;
	    min_ix = ptlbdp->pid_ix - conses;
	    if (min_ix < 0) {
		min_ix = 0;
		conses = ptlbdp->pid_ix;
	    }

	    if (conses) {
		hp = HAlloc(c_p, conses*2);
		ERTS_PTAB_LIST_DBG_SAVE_HEAP_ALLOC(ptlbdp, hp, conses*2);

		for (ix = ptlbdp->pid_ix - 1; ix >= min_ix; ix--) {
		    ERTS_PTAB_LIST_ASSERT(erts_ptab_is_valid_id(ptlbdp->pid[ix]));
		    res = CONS(hp, ptlbdp->pid[ix], res);
		    hp += 2;
		}

		ERTS_PTAB_LIST_DBG_VERIFY_HEAP_ALLOC_USED(ptlbdp, hp);
	    }

	    ptlbdp->pid_ix = min_ix;
	    if (min_ix == 0)
		ptlbdp->state = RETURN_RESULT;
	    else {
		ptlbdp->pid_sz = min_ix;
		ptlbdp->pid = erts_realloc(ERTS_ALC_T_PTAB_LIST_PIDS,
					   ptlbdp->pid,
					   sizeof(Eterm)*ptlbdp->pid_sz);
#if ERTS_PTAB_LIST_BIF_DEBUGLEVEL >= ERTS_PTAB_LIST_DBGLVL_CHK_FOUND_PIDS
		ptlbdp->debug.pid_started
		    = erts_realloc(ERTS_ALC_T_PTAB_LIST_PIDS,
				   ptlbdp->debug.pid_started,
				   sizeof(Uint64) * ptlbdp->pid_sz);
#endif
	    }
	    reds = conses/ERTS_PTAB_LIST_BIF_BUILD_RESULT_CONSES_PER_RED;
	    BUMP_REDS(c_p, reds);
	    have_reds -= reds;

	    ERTS_PTAB_LIST_DBG_CHK_RESLIST(res);
	    ERTS_PTAB_LIST_DBG_TRACE(c_p->common.id, end_build_res);
	    *res_accp = res;
	    break;
	}
	case RETURN_RESULT:
	    cleanup_ptab_list_bif_data(mbp);
	    return 1;

	default:
	    erts_exit(ERTS_ABORT_EXIT,
		     "%s:%d:ptab_list_bif_engine(): Invalid state: %d\n",
		     __FILE__, __LINE__, (int) ptlbdp->state);
	}

	
    } while (have_reds || ptlbdp->state == RETURN_RESULT);

    return 0;
}

/*
 * ptab_list_continue/2 is a hidden BIF that the original BIF traps to
 * if there are too much work to do in one go.
 */

static BIF_RETTYPE ptab_list_continue(BIF_ALIST_2)
{
    Eterm res_acc;
    Binary *mbp;

    /*
     * This bif cannot be called from erlang code. It can only be
     * trapped to from other BIFs; therefore, a bad argument
     * is an internal error and should never occur...
     */

    ERTS_PTAB_LIST_DBG_TRACE(BIF_P->common.id, call);
    ERTS_PTAB_LIST_ASSERT(is_nil(BIF_ARG_1) || is_list(BIF_ARG_1));

    res_acc = BIF_ARG_1;

    mbp = erts_magic_ref2bin(BIF_ARG_2);

    ERTS_PTAB_LIST_ASSERT(ERTS_MAGIC_BIN_DESTRUCTOR(mbp)
			  == cleanup_ptab_list_bif_data);
    ERTS_PTAB_LIST_ASSERT(
	((ErtsPTabListBifData *) ERTS_MAGIC_BIN_DATA(mbp))->debug.caller
	== BIF_P->common.id);

    if (ptab_list_bif_engine(BIF_P, &res_acc, mbp)) {
	ERTS_PTAB_LIST_DBG_TRACE(BIF_P->common.id, return);
	BIF_RET(res_acc);
    }
    else {
	ERTS_PTAB_LIST_DBG_TRACE(BIF_P->common.id, trap);
	ERTS_BIF_YIELD2(&ptab_list_continue_export, BIF_P, res_acc, BIF_ARG_2);
    }
}

void
erts_ptab_init(void)
{
    /* ptab_list_continue/2 is a hidden BIF that the original BIF traps to. */
    erts_init_trap_export(&ptab_list_continue_export,
			  am_erlang, am_ptab_list_continue, 2,
			  &ptab_list_continue);

}

/*
 * Debug stuff
 */

static void assert_ptab_consistency(ErtsPTab *ptab)
{
#ifdef DEBUG
    if (ptab->r.o.free_id_data) {
	Uint32 ix, pix;
        Uint data;
	int free_pids = 0;
	int null_slots = 0;
	
	for (ix=0; ix < ptab->r.o.max; ix++) {
	    if (erts_atomic_read_nob(&ptab->r.o.free_id_data[ix]) != ptab->r.o.invalid_data) {
		++free_pids;
		data = erts_atomic_read_nob(&ptab->r.o.free_id_data[ix]);
		pix = erts_ptab_data2pix(ptab, (Eterm) data);
		ASSERT(erts_ptab_pix2intptr_nob(ptab, pix) == ERTS_AINT_NULL);
	    }
	    if (erts_atomic_read_nob(&ptab->r.o.tab[ix]) == ERTS_AINT_NULL) {
		++null_slots;
	    }
	}	
	ASSERT(free_pids == null_slots);
	ASSERT(free_pids == ptab->r.o.max - erts_atomic32_read_nob(&ptab->vola.tile.count));
    }
#endif
}

Sint
erts_ptab_test_next_id(ErtsPTab *ptab, int set, Uint next)
{
    Uint64 ld;
    Sint res;
    Eterm data;
    int first_pix = -1;

    erts_ptab_rwlock(ptab);

    assert_ptab_consistency(ptab);

    if (ptab->r.o.free_id_data) {
	Uint32 id_ix, dix;

	if (set) {
	    Uint32 i, max_ix, stop_id_ix;
            Uint num;
	    max_ix = ptab->r.o.max - 1;
	    num = next;
	    id_ix = (Uint32) erts_atomic32_read_nob(&ptab->vola.tile.aid_ix);

	    for (i=0; i <= max_ix; ++i) {
		Uint32 pix;
		++num;
		num &= ~(~UWORD_CONSTANT(0) << ERTS_PTAB_ID_DATA_SIZE);
		if (num == ptab->r.o.invalid_data) {
		    num += ptab->r.o.max;
		    num &= ~(~UWORD_CONSTANT(0) << ERTS_PTAB_ID_DATA_SIZE);
		}
		pix = erts_ptab_data2pix(ptab, num);
		if (ERTS_AINT_NULL == erts_ptab_pix2intptr_nob(ptab, pix)) {
		    ++id_ix;
		    dix = ix_to_free_id_data_ix(ptab, id_ix);
		    erts_atomic_set_nob(&ptab->r.o.free_id_data[dix], num);
		    ASSERT(pix == erts_ptab_data2pix(ptab, num));
		}
	    }
	    erts_atomic32_set_nob(&ptab->vola.tile.fid_ix, id_ix);

	    /* Write invalid_data in rest of free_id_data[]: */
	    stop_id_ix = (1 + erts_atomic32_read_nob(&ptab->vola.tile.aid_ix)) & max_ix;
	    while (1) {
		id_ix = (id_ix+1) & max_ix;
		if (id_ix == stop_id_ix)
		    break;
		dix = ix_to_free_id_data_ix(ptab, id_ix);
		erts_atomic_set_nob(&ptab->r.o.free_id_data[dix],
                                    ptab->r.o.invalid_data);
	    }
	}
	id_ix = (Uint32) erts_atomic32_read_nob(&ptab->vola.tile.aid_ix) + 1;
	dix = ix_to_free_id_data_ix(ptab, id_ix);
	res = (Sint) erts_atomic_read_nob(&ptab->r.o.free_id_data[dix]);
    }
    else {
	/* Deprecated legacy algorithm... */
	if (!set)
	    ld = last_data_read_nob(ptab);
	else {

	    ld = (Uint64) next;
	    data = ERTS_PTAB_LastData2EtermData(ld);
	    if (ptab->r.o.invalid_data == data) {
		ld += ptab->r.o.max;
		ASSERT(erts_ptab_data2pix(ptab, data)
		       == erts_ptab_data2pix(ptab,
					     ERTS_PTAB_LastData2EtermData(ld)));
	    }
	    last_data_set_relb(ptab, ld);
	}

	while (1) {
	    int pix;
	    ld++;
	    pix = (int) (ld % ptab->r.o.max);
	    if (first_pix < 0)
		first_pix = pix;
	    else if (pix == first_pix) {
		res = -1;
		break;
	    }
	    if (ERTS_AINT_NULL == erts_ptab_pix2intptr_nob(ptab, pix)) {
		data = ERTS_PTAB_LastData2EtermData(ld);
		if (ptab->r.o.invalid_data == data) {
		    ld += ptab->r.o.max;
		    ASSERT(erts_ptab_data2pix(ptab, data)
			   == erts_ptab_data2pix(ptab,
						 ERTS_PTAB_LastData2EtermData(ld)));
		    data = ERTS_PTAB_LastData2EtermData(ld);
		}
		res = data;
		break;
	    }
	}
    }

    assert_ptab_consistency(ptab);
    erts_ptab_rwunlock(ptab);

    return res;
}

static ERTS_INLINE ErtsPTabElementCommon *
ptab_pix2el(ErtsPTab *ptab, int ix)
{
    ErtsPTabElementCommon *ptab_el;
    ASSERT(0 <= ix && ix < ptab->r.o.max);
    ptab_el = (ErtsPTabElementCommon *) erts_ptab_pix2intptr_nob(ptab, ix);
    if (ptab_el == ptab->r.o.invalid_element)
	return NULL;
    else
	return ptab_el;
}

Eterm
erts_debug_ptab_list(Process *c_p, ErtsPTab *ptab)
{
    int i;
    Uint need;
    Eterm res;
    Eterm* hp;
    Eterm *hp_end;

    erts_ptab_rwlock(ptab);

    res = NIL;
    need = erts_ptab_count(ptab) * 2;
    hp = HAlloc(c_p, need); /* we need two heap words for each id */
    hp_end = hp + need;
     
    /* make the list by scanning backward */


    for (i = ptab->r.o.max-1; i >= 0; i--) {
	ErtsPTabElementCommon *el = ptab_pix2el(ptab, i);
	if (el) {
	    res = CONS(hp, el->id, res);
	    hp += 2;
	}
    }

    erts_ptab_rwunlock(ptab);

    HRelease(c_p, hp_end, hp);

    return res;
}

Eterm
erts_debug_ptab_list_bif_info(Process *c_p, ErtsPTab *ptab)
{
    ERTS_DECL_AM(ptab_list_bif_info);
    Eterm elements[] = {
	AM_ptab_list_bif_info,
	make_small((Uint) ERTS_PTAB_LIST_BIF_MIN_START_REDS),
	make_small((Uint) ptab->list.data.chunks),
	make_small((Uint) ERTS_PTAB_LIST_BIF_TAB_CHUNK_SIZE),
	make_small((Uint) ERTS_PTAB_LIST_BIF_TAB_INSPECT_INDICES_PER_RED),
	make_small((Uint) ERTS_PTAB_LIST_BIF_TAB_FREE_DELETED_REDS),
	make_small((Uint) ERTS_PTAB_LIST_BIF_INSPECT_DELETED_PER_RED),
	make_small((Uint) ERTS_PTAB_LIST_INSPECT_DELETED_MAX_REDS),
	make_small((Uint) ERTS_PTAB_LIST_BIF_BUILD_RESULT_CONSES_PER_RED),
	make_small((Uint) ERTS_PTAB_LIST_BIF_DEBUGLEVEL)
    };
    Uint sz = 0;
    Eterm *hp;
    (void) erts_bld_tuplev(NULL, &sz, sizeof(elements)/sizeof(Eterm), elements);
    hp = HAlloc(c_p, sz);
    return erts_bld_tuplev(&hp, NULL, sizeof(elements)/sizeof(Eterm), elements);
}

#if ERTS_PTAB_LIST_BIF_DEBUGLEVEL >= ERTS_PTAB_LIST_DBGLVL_CHK_FOUND_PIDS
static void
debug_ptab_list_check_found_pid(ErtsPTabListBifData *ptlbdp,
				Eterm pid,
				Uint64 ic,
				int pid_should_be_found)
{
    int i;
    for (i = 0; i < ptlbdp->pid_ix; i++) {
	if (ptlbdp->pid[i] == pid && ptlbdp->debug.pid_started[i] == ic) {
	    ERTS_PTAB_LIST_ASSERT(pid_should_be_found);
	    return;
	}
    }
    ERTS_PTAB_LIST_ASSERT(!pid_should_be_found);
}
#endif

#if ERTS_PTAB_LIST_BIF_DEBUGLEVEL >= ERTS_PTAB_LIST_DBGLVL_CHK_RESLIST
static void
debug_ptab_list_check_res_list(Eterm list)
{
    while (is_list(list)) {
	Eterm* consp = list_val(list);
	Eterm hd = CAR(consp);
	ERTS_PTAB_LIST_ASSERT(erts_ptab_is_valid_id(hd));
	list = CDR(consp);
    }

    ERTS_PTAB_LIST_ASSERT(is_nil(list));
}
#endif

#if ERTS_PTAB_LIST_BIF_DEBUGLEVEL >= ERTS_PTAB_LIST_DBGLVL_CHK_PIDS

static void
debug_ptab_list_save_all_pids(ErtsPTabListBifData *ptlbdp)
{
    int ix, tix, cpix;
    ErtsPTab *ptab = ptlbdp->ptab;
    ptlbdp->debug.correct_pids_verified = 0;
    ptlbdp->debug.correct_pids = erts_alloc(ERTS_ALC_T_PTAB_LIST_PIDS,
					    sizeof(Eterm)*ptlbdp->pid_sz);

    for (tix = 0, cpix = 0; tix < ptab->r.o.max; tix++) {
	ErtsPTabElementCommon *el = ptab_pix2el(ptab, tix);
	if (el) {
	    ERTS_PTAB_LIST_ASSERT(erts_ptab_is_valid_id(el->id));
	    ptlbdp->debug.correct_pids[cpix++] = el->id;
	    ERTS_PTAB_LIST_ASSERT(cpix <= ptlbdp->pid_sz);
	}
    }
    ERTS_PTAB_LIST_ASSERT(cpix == ptlbdp->pid_sz);

    for (ix = 0; ix < ptlbdp->pid_sz; ix++)
	ptlbdp->pid[ix] = make_small(ix);
}

static void
debug_ptab_list_verify_all_pids(ErtsPTabListBifData *ptlbdp)
{
    int ix, cpix;

    ERTS_PTAB_LIST_ASSERT(ptlbdp->pid_ix == ptlbdp->pid_sz);

    for (ix = 0; ix < ptlbdp->pid_sz; ix++) {
	int found = 0;
	Eterm pid = ptlbdp->pid[ix];
	ERTS_PTAB_LIST_ASSERT(erts_ptab_is_valid_id(pid));
	for (cpix = ix; cpix < ptlbdp->pid_sz; cpix++) {
	    if (ptlbdp->debug.correct_pids[cpix] == pid) {
		ptlbdp->debug.correct_pids[cpix] = NIL;
		found = 1;
		break;
	    }
	}
	if (!found) {
	    for (cpix = 0; cpix < ix; cpix++) {
		if (ptlbdp->debug.correct_pids[cpix] == pid) {
		    ptlbdp->debug.correct_pids[cpix] = NIL;
		    found = 1;
		    break;
		}
	    }
	}
	ERTS_PTAB_LIST_ASSERT(found);
    }
    ptlbdp->debug.correct_pids_verified = 1;

    erts_free(ERTS_ALC_T_PTAB_LIST_PIDS, ptlbdp->debug.correct_pids);
    ptlbdp->debug.correct_pids = NULL;
}
#endif /* ERTS_PTAB_LIST_BIF_DEBUGLEVEL >= ERTS_PTAB_LIST_DBGLVL_CHK_PIDS */

#if ERTS_PTAB_LIST_BIF_DEBUGLEVEL >= ERTS_PTAB_LIST_DBGLVL_CHK_DEL_LIST
static void
debug_ptab_list_check_del_list(ErtsPTab *ptab)
{
    ERTS_LC_ASSERT(erts_lc_ptab_is_rwlocked(ptab));
    if (!ptab->list.data.deleted.start)
	ERTS_PTAB_LIST_ASSERT(!ptab->list.data.deleted.end);
    else {
	Uint64 curr_interval = erts_current_interval_nob(erts_ptab_interval(ptab));
	Uint64 *prev_x_interval_p = NULL;
	ErtsPTabDeletedElement *ptdep;

	for (ptdep = ptab->list.data.deleted.start;
	     ptdep;
	     ptdep = ptdep->next) {
	    if (!ptdep->prev)
		ERTS_PTAB_LIST_ASSERT(ptab->list.data.deleted.start == ptdep);
	    else
		ERTS_PTAB_LIST_ASSERT(ptdep->prev->next == ptdep);
	    if (!ptdep->next)
		ERTS_PTAB_LIST_ASSERT(ptab->list.data.deleted.end == ptdep);
	    else
		ERTS_PTAB_LIST_ASSERT(ptdep->next->prev == ptdep);
	    if (ptdep->ix < 0) {
		Uint64 interval = ptdep->u.bif_invocation.interval;
		ERTS_PTAB_LIST_ASSERT(interval <= curr_interval);
	    }
	    else {
		Uint64 s_interval = ptdep->u.element.inserted;
		Uint64 x_interval = ptdep->u.element.deleted;
		
		ERTS_PTAB_LIST_ASSERT(s_interval <= x_interval);
		if (prev_x_interval_p)
		    ERTS_PTAB_LIST_ASSERT(*prev_x_interval_p <= x_interval);
		prev_x_interval_p = &ptdep->u.element.deleted;
		ERTS_PTAB_LIST_ASSERT(
		    erts_ptab_is_valid_id(ptdep->u.element.id));
		ERTS_PTAB_LIST_ASSERT(erts_ptab_id2pix(ptab,
						       ptdep->u.element.id)
				      == ptdep->ix);

	    }
	}
	
    }
}

static void
debug_ptab_list_check_del_free_list(ErtsPTab *ptab,
				    ErtsPTabDeletedElement *free_list)
{
    if (ptab->list.data.deleted.start) {
	ErtsPTabDeletedElement *fptdep;
	ErtsPTabDeletedElement *ptdep;

	for (fptdep = free_list; fptdep; fptdep = fptdep->next) {
	    for (ptdep = ptab->list.data.deleted.start;
		 ptdep;
		 ptdep = ptdep->next) {
		ERTS_PTAB_LIST_ASSERT(fptdep != ptdep);
	    }
	}
    }
}

#endif

#if ERTS_PTAB_LIST_BIF_DEBUGLEVEL != 0

static void
debug_ptab_list_assert_error(char* expr, const char* file, int line, const char *func)
{   
    fflush(stdout);
    erts_fprintf(stderr, "%s:%d:%s(): Assertion failed: %s\n",
		 (char *) file, line, (char *) func, expr);
    fflush(stderr);
    abort();
}

#endif
