/*
 * %CopyrightBegin%
 * 
 * Copyright Ericsson AB 1996-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%
 */

#define ERL_PROCESS_C__

#ifdef HAVE_CONFIG_H
#  include "config.h"
#endif

#include <stddef.h> /* offsetof() */
#include <ctype.h>
#include "sys.h"
#include "erl_vm.h"
#include "global.h"
#include "erl_process.h"
#include "erl_nmgc.h"
#include "error.h"
#include "bif.h"
#include "erl_db.h"
#include "dist.h"
#include "beam_catches.h"
#include "erl_instrument.h"
#include "erl_threads.h"
#include "erl_binary.h"

#define ERTS_RUNQ_CHECK_BALANCE_REDS_PER_SCHED (2000*CONTEXT_REDS)
#define ERTS_RUNQ_CALL_CHECK_BALANCE_REDS \
  (ERTS_RUNQ_CHECK_BALANCE_REDS_PER_SCHED/2)

#define ERTS_PROC_MIN_CONTEXT_SWITCH_REDS_COST (CONTEXT_REDS/10)

#define ERTS_SCHED_SLEEP_SPINCOUNT 10000

#define ERTS_WAKEUP_OTHER_LIMIT (100*CONTEXT_REDS/2)
#define ERTS_WAKEUP_OTHER_DEC 10
#define ERTS_WAKEUP_OTHER_FIXED_INC (CONTEXT_REDS/10)

#define ERTS_MAX_CPU_TOPOLOGY_ID ((int) 0xffff)

#if 0 || defined(DEBUG)
#define ERTS_FAKE_SCHED_BIND_PRINT_SORTED_CPU_DATA
#endif

#if defined(DEBUG) && 0
#define HARDDEBUG
#else
#undef HARDDEBUG
#endif

#ifdef HARDDEBUG
#define HARDDEBUG_RUNQS
#endif

#ifdef HIPE
#include "hipe_mode_switch.h"	/* for hipe_init_process() */
#include "hipe_signal.h"	/* for hipe_thread_signal_init() */
#endif

#ifdef ERTS_ENABLE_LOCK_COUNT
#include "erl_lock_count.h"
#endif

#define MAX_BIT       (1 << PRIORITY_MAX)
#define HIGH_BIT      (1 << PRIORITY_HIGH)
#define NORMAL_BIT    (1 << PRIORITY_NORMAL)
#define LOW_BIT       (1 << PRIORITY_LOW)

#define ERTS_MAYBE_SAVE_TERMINATING_PROCESS(P)			\
do {								\
    ERTS_SMP_LC_ASSERT(erts_lc_mtx_is_locked(&proc_tab_mtx));	\
    if (saved_term_procs.end)					\
	save_terminating_process((P));				\
} while (0)

#define ERTS_EMPTY_RUNQ(RQ) \
  ((RQ)->len == 0 && (RQ)->misc.start == NULL)

extern Eterm beam_apply[];
extern Eterm beam_exit[];
extern Eterm beam_continue_exit[];

static Sint p_last;
static Sint p_next;
static Sint p_serial;
static Uint p_serial_mask;
static Uint p_serial_shift;

Uint erts_no_schedulers;
Uint erts_max_processes = ERTS_DEFAULT_MAX_PROCESSES;
Uint erts_process_tab_index_mask;

int erts_sched_thread_suggested_stack_size = -1;

#ifdef ERTS_ENABLE_LOCK_CHECK
ErtsLcPSDLocks erts_psd_required_locks[ERTS_PSD_SIZE];
#endif

#ifdef ERTS_SMP

int erts_disable_proc_not_running_opt;

#define ERTS_SCHED_CHANGING_ONLINE 1
#define ERTS_SCHED_CHANGING_MULTI_SCHED 2

static struct {
    erts_smp_mtx_t mtx;
    erts_smp_cnd_t cnd;
    int changing;
    int online;
    int curr_online;
    int wait_curr_online;
    erts_smp_atomic_t active;
    struct {
	erts_smp_atomic_t ongoing;
	long wait_active;
	ErtsProcList *procs;
    } msb; /* Multi Scheduling Block */
} schdlr_sspnd;

static struct {
    erts_smp_mtx_t update_mtx;
    erts_smp_atomic_t active_runqs;
    int last_active_runqs;
    erts_smp_atomic_t used_runqs;
    int forced_check_balance;
    erts_smp_atomic_t checking_balance;
    int halftime;
    int full_reds_history_index;
    struct {
	int active_runqs;
	int reds;
	int max_len;
    } prev_rise;
    Uint n;
} balance_info;

#define ERTS_BLNCE_SAVE_RISE(ACTIVE, MAX_LEN, REDS)	\
do {							\
    balance_info.prev_rise.active_runqs = (ACTIVE);	\
    balance_info.prev_rise.max_len = (MAX_LEN);		\
    balance_info.prev_rise.reds = (REDS);		\
} while (0)

#endif

/*
 * Cpu topology hierarchy.
 */
#define ERTS_TOPOLOGY_NODE		0
#define ERTS_TOPOLOGY_PROCESSOR		1
#define ERTS_TOPOLOGY_PROCESSOR_NODE	2
#define ERTS_TOPOLOGY_CORE		3
#define ERTS_TOPOLOGY_THREAD		4
#define ERTS_TOPOLOGY_LOGICAL		5

#define ERTS_TOPOLOGY_MAX_DEPTH		6

typedef struct {
    int bind_id;
    int bound_id;
} ErtsCpuBindData;

static ErtsCpuBindData *scheduler2cpu_map;
erts_smp_rwmtx_t erts_cpu_bind_rwmtx;

typedef enum {
    ERTS_CPU_BIND_SPREAD,
    ERTS_CPU_BIND_PROCESSOR_SPREAD,
    ERTS_CPU_BIND_THREAD_SPREAD,
    ERTS_CPU_BIND_THREAD_NO_NODE_PROCESSOR_SPREAD,
    ERTS_CPU_BIND_NO_NODE_PROCESSOR_SPREAD,
    ERTS_CPU_BIND_NO_NODE_THREAD_SPREAD,
    ERTS_CPU_BIND_NO_SPREAD,
    ERTS_CPU_BIND_NONE
} ErtsCpuBindOrder;

ErtsCpuBindOrder cpu_bind_order;

static erts_cpu_topology_t *user_cpudata;
static int user_cpudata_size;
static erts_cpu_topology_t *system_cpudata;
static int system_cpudata_size;

erts_sched_stat_t erts_sched_stat;

ErtsRunQueue *erts_common_run_queue;

#ifdef USE_THREADS
static erts_tsd_key_t sched_data_key;
#endif

static erts_smp_mtx_t proc_tab_mtx;

static erts_smp_atomic_t function_calls;

#ifdef ERTS_SMP
static erts_smp_atomic_t doing_sys_schedule;
static erts_smp_atomic_t no_empty_run_queues;
#else /* !ERTS_SMP */
ErtsSchedulerData *erts_scheduler_data;
#endif

ErtsAlignedRunQueue *erts_aligned_run_queues;
Uint erts_no_run_queues;

typedef union {
    ErtsSchedulerData esd;
    char align[ERTS_ALC_CACHE_LINE_ALIGN_SIZE(sizeof(ErtsSchedulerData))];
} ErtsAlignedSchedulerData;

ErtsAlignedSchedulerData *erts_aligned_scheduler_data;

#ifndef BM_COUNTERS
static int processes_busy;
#endif

Process**  process_tab;
static Uint last_reductions;
static Uint last_exact_reductions;
Uint erts_default_process_flags;
Eterm erts_system_monitor;
Eterm erts_system_monitor_msg_queue_len;
Eterm erts_system_monitor_long_gc;
Eterm erts_system_monitor_large_heap;
struct erts_system_monitor_flags_t erts_system_monitor_flags;

/* system performance monitor */
Eterm erts_system_profile;
struct erts_system_profile_flags_t erts_system_profile_flags;

#ifdef HYBRID
Uint erts_num_active_procs;
Process** erts_active_procs;
#endif

static erts_smp_atomic_t process_count;

typedef struct ErtsTermProcElement_ ErtsTermProcElement;
struct ErtsTermProcElement_ {
    ErtsTermProcElement *next;
    ErtsTermProcElement *prev;
    int ix;
    union {
	struct {
	    Eterm pid;
	    SysTimeval spawned;
	    SysTimeval exited;
	} process;
	struct {
	    SysTimeval time;
	} bif_invocation;
    } u;
};

static struct {
    ErtsTermProcElement *start;
    ErtsTermProcElement *end;
} saved_term_procs;

ERTS_SCHED_PREF_QUICK_ALLOC_IMPL(misc_op_list,
				 ErtsMiscOpList,
				 10,
				 ERTS_ALC_T_MISC_OP_LIST)

ERTS_SCHED_PREF_QUICK_ALLOC_IMPL(proclist,
				 ErtsProcList,
				 200,
				 ERTS_ALC_T_PROC_LIST)

#define ERTS_RUNQ_IX(IX)	(&erts_aligned_run_queues[(IX)].runq)
#define ERTS_SCHEDULER_IX(IX)	(&erts_aligned_scheduler_data[(IX)].esd)

#define ERTS_FOREACH_RUNQ(RQVAR, DO)					\
do {									\
    ErtsRunQueue *RQVAR;						\
    int ix__;								\
    for (ix__ = 0; ix__ < erts_no_run_queues; ix__++) {			\
	RQVAR = ERTS_RUNQ_IX(ix__);					\
	erts_smp_runq_lock(RQVAR);					\
	{ DO; }								\
	erts_smp_runq_unlock(RQVAR);					\
    }									\
} while (0)

#define ERTS_FOREACH_OP_RUNQ(RQVAR, DO)					\
do {									\
    ErtsRunQueue *RQVAR;						\
    int ix__;								\
    ERTS_SMP_LC_ASSERT(erts_smp_lc_mtx_is_locked(&schdlr_sspnd.mtx));	\
    for (ix__ = 0; ix__ < schdlr_sspnd.online; ix__++) {		\
	RQVAR = ERTS_RUNQ_IX(ix__);					\
	erts_smp_runq_lock(RQVAR);					\
	{ DO; }								\
	erts_smp_runq_unlock(RQVAR);					\
    }									\
} while (0)

#define ERTS_ATOMIC_FOREACH_RUNQ_X(RQVAR, DO, DOX)			\
do {									\
    ErtsRunQueue *RQVAR;						\
    int ix__;								\
    for (ix__ = 0; ix__ < erts_no_run_queues; ix__++) {			\
	RQVAR = ERTS_RUNQ_IX(ix__);					\
	erts_smp_runq_lock(RQVAR);					\
	{ DO; }								\
    }									\
    { DOX; }								\
    for (ix__ = 0; ix__ < erts_no_run_queues; ix__++)			\
	erts_smp_runq_unlock(ERTS_RUNQ_IX(ix__));			\
} while (0)

#define ERTS_ATOMIC_FOREACH_RUNQ(RQVAR, DO) \
  ERTS_ATOMIC_FOREACH_RUNQ_X(RQVAR, DO, )
/*
 * Local functions.
 */

static void init_processes_bif(void);
static void save_terminating_process(Process *p);
static void exec_misc_ops(ErtsRunQueue *);
static void print_function_from_pc(int to, void *to_arg, Eterm* x);
static int stack_element_dump(int to, void *to_arg, Process* p, Eterm* sp,
			      int yreg);
#ifdef ERTS_SMP
static void handle_pending_exiters(ErtsProcList *);

static void cpu_bind_order_sort(erts_cpu_topology_t *cpudata,
				int size,
				ErtsCpuBindOrder bind_order,
				int mk_seq);
static void signal_schedulers_bind_change(erts_cpu_topology_t *cpudata, int size);

#endif

static void early_cpu_bind_init(void);
static void late_cpu_bind_init(void);

#if defined(ERTS_SMP) && defined(ERTS_ENABLE_LOCK_CHECK)
int
erts_smp_lc_runq_is_locked(ErtsRunQueue *runq)
{
    return erts_smp_lc_mtx_is_locked(&runq->mtx);
}
#endif

void
erts_pre_init_process(void)
{
#ifdef USE_THREADS
    erts_tsd_key_create(&sched_data_key);
#endif

#ifdef ERTS_ENABLE_LOCK_CHECK
 {
     int ix;

     erts_psd_required_locks[ERTS_PSD_ERROR_HANDLER].get_locks
	 = ERTS_PSD_ERROR_HANDLER_BUF_GET_LOCKS;
     erts_psd_required_locks[ERTS_PSD_ERROR_HANDLER].set_locks
	 = ERTS_PSD_ERROR_HANDLER_BUF_SET_LOCKS;

     erts_psd_required_locks[ERTS_PSD_SAVED_CALLS_BUF].get_locks
	 = ERTS_PSD_SAVED_CALLS_BUF_GET_LOCKS;
     erts_psd_required_locks[ERTS_PSD_SAVED_CALLS_BUF].set_locks
	 = ERTS_PSD_SAVED_CALLS_BUF_SET_LOCKS;

     erts_psd_required_locks[ERTS_PSD_SCHED_ID].get_locks
	 = ERTS_PSD_SCHED_ID_GET_LOCKS;
     erts_psd_required_locks[ERTS_PSD_SCHED_ID].set_locks
	 = ERTS_PSD_SCHED_ID_SET_LOCKS;

     erts_psd_required_locks[ERTS_PSD_DIST_ENTRY].get_locks
	 = ERTS_PSD_DIST_ENTRY_GET_LOCKS;
     erts_psd_required_locks[ERTS_PSD_DIST_ENTRY].set_locks
	 = ERTS_PSD_DIST_ENTRY_GET_LOCKS;

     /* Check that we have locks for all entries */
     for (ix = 0; ix < ERTS_PSD_SIZE; ix++) {
	 ERTS_SMP_LC_ASSERT(erts_psd_required_locks[ix].get_locks);
	 ERTS_SMP_LC_ASSERT(erts_psd_required_locks[ix].set_locks);
     }
 }
#endif
}

/* initialize the scheduler */
void
erts_init_process(void)
{
    Uint proc_bits = ERTS_PROC_BITS;

#ifdef ERTS_SMP
    erts_disable_proc_not_running_opt = 0;
    erts_init_proc_lock();
#endif

    init_proclist_alloc();

    erts_smp_atomic_init(&process_count, 0);

    if (erts_use_r9_pids_ports) {
	proc_bits = ERTS_R9_PROC_BITS;
	ASSERT(erts_max_processes <= (1 << ERTS_R9_PROC_BITS));
    }

    process_tab = (Process**) erts_alloc(ERTS_ALC_T_PROC_TABLE,
					 erts_max_processes*sizeof(Process*));
    sys_memzero(process_tab, erts_max_processes * sizeof(Process*));
#ifdef HYBRID
    erts_active_procs = (Process**)
        erts_alloc(ERTS_ALC_T_ACTIVE_PROCS,
                   erts_max_processes * sizeof(Process*));
    erts_num_active_procs = 0;
#endif

    erts_smp_mtx_init(&proc_tab_mtx, "proc_tab");
    p_last = -1;
    p_next = 0;
    p_serial = 0;

    p_serial_shift = erts_fit_in_bits(erts_max_processes - 1);
    p_serial_mask = ((~(~((Uint) 0) << proc_bits)) >> p_serial_shift);
    erts_process_tab_index_mask = ~(~((Uint) 0) << p_serial_shift);
#ifndef BM_COUNTERS
    processes_busy = 0;
#endif
    last_reductions = 0;
    last_exact_reductions = 0;
    erts_default_process_flags = 0;
}

void
erts_late_init_process(void)
{
    int ix;
    init_processes_bif();

    erts_smp_spinlock_init(&erts_sched_stat.lock, "sched_stat");
    for (ix = 0; ix < ERTS_NO_PRIO_LEVELS; ix++) {
	Eterm atom;
	char *atom_str;
	switch (ix) {
	case PRIORITY_MAX:
	    atom_str = "process_max";
	    break;
	case PRIORITY_HIGH:
	    atom_str = "process_high";
	    break;
	case PRIORITY_NORMAL:
	    atom_str = "process_normal";
	    break;
	case PRIORITY_LOW:
	    atom_str = "process_low";
	    break;
	case ERTS_PORT_PRIO_LEVEL:
	    atom_str = "port";
	    break;
	default:
	    atom_str = "bad_prio";
	    ASSERT(!"bad prio");
	    break;
	}
	atom = am_atom_put(atom_str, sys_strlen(atom_str));
	erts_sched_stat.prio[ix].name = atom;
	erts_sched_stat.prio[ix].total_executed = 0;
	erts_sched_stat.prio[ix].executed = 0;
	erts_sched_stat.prio[ix].total_migrated = 0;
	erts_sched_stat.prio[ix].migrated = 0;
    }

}

static ERTS_INLINE ErtsProcList *
proclist_create(Process *p)
{
    ErtsProcList *plp = proclist_alloc();
    plp->pid = p->id;
    plp->started = p->started;
    return plp;
}

static ERTS_INLINE void
proclist_destroy(ErtsProcList *plp)
{
    proclist_free(plp);
}

static ERTS_INLINE int
proclist_same(ErtsProcList *plp, Process *p)
{
    return (plp->pid == p->id
	    && erts_cmp_timeval(&plp->started, &p->started) == 0);
}

ErtsProcList *
erts_proclist_create(Process *p)
{
    return proclist_create(p);
}

void
erts_proclist_destroy(ErtsProcList *plp)
{
    proclist_destroy(plp);
}

int
erts_proclist_same(ErtsProcList *plp, Process *p)
{
    return proclist_same(plp, p);
}

void *
erts_psd_set_init(Process *p, ErtsProcLocks plocks, int ix, void *data)
{
    void *old;
    ErtsProcLocks xplocks;
    int refc = 0;
    ErtsPSD *psd = erts_alloc(ERTS_ALC_T_PSD, sizeof(ErtsPSD));
    int i;
    for (i = 0; i < ERTS_PSD_SIZE; i++)
	psd->data[i] = NULL;

    ERTS_SMP_LC_ASSERT(plocks);
    ERTS_SMP_LC_ASSERT(plocks == erts_proc_lc_my_proc_locks(p));

    xplocks = ERTS_PROC_LOCKS_ALL;
    xplocks &= ~plocks;
    if (xplocks && erts_smp_proc_trylock(p, xplocks) == EBUSY) {
	if (xplocks & ERTS_PROC_LOCK_MAIN) {
	    erts_smp_proc_inc_refc(p);
	    erts_smp_proc_unlock(p, plocks);
	    erts_smp_proc_lock(p, ERTS_PROC_LOCKS_ALL);
	    refc = 1;
	}
	else {
	    if (plocks & ERTS_PROC_LOCKS_ALL_MINOR)
		erts_smp_proc_unlock(p, plocks & ERTS_PROC_LOCKS_ALL_MINOR);
	    erts_smp_proc_lock(p, ERTS_PROC_LOCKS_ALL_MINOR);
	}
    }
    if (!p->psd)
	p->psd = psd;
    if (xplocks)
	erts_smp_proc_unlock(p, xplocks);
    if (refc)
	erts_smp_proc_dec_refc(p);
    ASSERT(p->psd);
    if (p->psd != psd)
	erts_free(ERTS_ALC_T_PSD, psd);
    old = p->psd->data[ix];
    p->psd->data[ix] = data;
    ERTS_SMP_LC_ASSERT(plocks == erts_proc_lc_my_proc_locks(p));
    return old;
}

#ifdef ERTS_SMP

static void
prepare_for_block(void *vrq)
{
    erts_smp_runq_unlock((ErtsRunQueue *) vrq);
}

static void
resume_after_block(void *vrq)
{
    erts_smp_runq_lock((ErtsRunQueue *) vrq);
}

#endif

static ERTS_INLINE void
sched_waiting_sys(Uint no, ErtsRunQueue *rq)
{
    ERTS_SMP_LC_ASSERT(erts_smp_lc_runq_is_locked(rq));
    ASSERT(rq->waiting >= 0);
    rq->flags |= (ERTS_RUNQ_FLG_OUT_OF_WORK
		  | ERTS_RUNQ_FLG_HALFTIME_OUT_OF_WORK);
    rq->waiting++;
    rq->waiting *= -1;
    rq->woken = 0;
    if (erts_system_profile_flags.scheduler)
	profile_scheduler(make_small(no), am_inactive);
}

static ERTS_INLINE void
sched_active_sys(Uint no, ErtsRunQueue *rq)
{
    ERTS_SMP_LC_ASSERT(erts_smp_lc_runq_is_locked(rq));
    ASSERT(rq->waiting < 0);
    rq->waiting *= -1;
    rq->waiting--;
    if (erts_system_profile_flags.scheduler)
	profile_scheduler(make_small(no), am_active);
}

Uint
erts_active_schedulers(void)
{
    /* RRRRRRRRR */

    Uint as = erts_no_schedulers;

    ERTS_ATOMIC_FOREACH_RUNQ(rq, as -= abs(rq->waiting));

    ASSERT(as >= 0);
    return as;
}

#ifdef ERTS_SMP

static ERTS_INLINE void
sched_waiting(Uint no, ErtsRunQueue *rq)
{
    ERTS_SMP_LC_ASSERT(erts_smp_lc_runq_is_locked(rq));
    rq->flags |= (ERTS_RUNQ_FLG_OUT_OF_WORK
		  | ERTS_RUNQ_FLG_HALFTIME_OUT_OF_WORK);
    if (rq->waiting < 0)
	rq->waiting--;
    else
	rq->waiting++;
    rq->woken = 0;
    if (erts_system_profile_flags.scheduler)
	profile_scheduler(make_small(no), am_inactive);
}

static ERTS_INLINE void
sched_active(Uint no, ErtsRunQueue *rq)
{
    ERTS_SMP_LC_ASSERT(erts_smp_lc_runq_is_locked(rq));
    if (rq->waiting < 0)
	rq->waiting++;
    else
	rq->waiting--;
    if (erts_system_profile_flags.scheduler)
	profile_scheduler(make_small(no), am_active);
}

static int ERTS_INLINE
ongoing_multi_scheduling_block(void)
{
    return erts_smp_atomic_read(&schdlr_sspnd.msb.ongoing) != 0;
}

static ERTS_INLINE void
empty_runq(ErtsRunQueue *rq)
{
    long oifls = erts_smp_atomic_band(&rq->info_flags, ~ERTS_RUNQ_IFLG_NONEMPTY);
    if (oifls & ERTS_RUNQ_IFLG_NONEMPTY) {
#ifdef DEBUG
	long empty = erts_smp_atomic_read(&no_empty_run_queues);
	ASSERT(0 <= empty && empty < erts_no_run_queues);
#endif
	erts_smp_atomic_inc(&no_empty_run_queues);
    }
}

static ERTS_INLINE void
non_empty_runq(ErtsRunQueue *rq)
{
    long oifls = erts_smp_atomic_bor(&rq->info_flags, ERTS_RUNQ_IFLG_NONEMPTY);
    if (!(oifls & ERTS_RUNQ_IFLG_NONEMPTY)) {
#ifdef DEBUG
	long empty = erts_smp_atomic_read(&no_empty_run_queues);
	ASSERT(0 < empty && empty <= erts_no_run_queues);
#endif
	erts_smp_atomic_dec(&no_empty_run_queues);
    }
}

static ERTS_INLINE int
sched_spin_wake(ErtsRunQueue *rq)
{
#if ERTS_SCHED_SLEEP_SPINCOUNT == 0
    return 0;
#else
    long val;
    ERTS_SMP_LC_ASSERT(erts_smp_lc_runq_is_locked(rq));

    val = erts_smp_atomic_read(&rq->spin_waiter);
    ASSERT(val >= 0);
    if (val != 0) {
	erts_smp_atomic_inc(&rq->spin_wake);
	return 1;
    }
    return 0;
#endif
}

static ERTS_INLINE int
sched_spin_wake_all(ErtsRunQueue *rq)
{
#if ERTS_SCHED_SLEEP_SPINCOUNT == 0
    return 0;
#else
    long val;
    ERTS_SMP_LC_ASSERT(erts_smp_lc_runq_is_locked(rq));

    val = erts_smp_atomic_read(&rq->spin_waiter);
    ASSERT(val >= 0);
    if (val != 0)
	erts_smp_atomic_add(&rq->spin_wake, val);
    return val;
#endif
}

static void
sched_sys_wait(Uint no, ErtsRunQueue *rq)
{
    long dt;
#if ERTS_SCHED_SLEEP_SPINCOUNT != 0
    int val;
    int spincount = ERTS_SCHED_SLEEP_SPINCOUNT;
    ERTS_SMP_LC_ASSERT(erts_smp_lc_runq_is_locked(rq));

#endif

    sched_waiting_sys(no, rq);

#if ERTS_SCHED_SLEEP_SPINCOUNT != 0
    erts_smp_atomic_inc(&rq->spin_waiter);
    erts_smp_runq_unlock(rq);

    erl_sys_schedule(1); /* Might give us something to do */

    dt = do_time_read_and_reset();
    if (dt) bump_timer(dt);

    while (spincount-- > 0) {
	val = erts_smp_atomic_read(&rq->spin_wake);
	ASSERT(val >= 0);
	if (val != 0) {
	    erts_smp_runq_lock(rq);
	    val = erts_smp_atomic_read(&rq->spin_wake);
	    ASSERT(val >= 0);
	    if (val != 0)
		goto woken;
	    if (spincount == 0)
		goto sleep;
	    erts_smp_runq_unlock(rq);
	}
    }

    erts_smp_runq_lock(rq);
    val = erts_smp_atomic_read(&rq->spin_wake);
    ASSERT(val >= 0);
    if (val != 0) {
    woken:
	erts_smp_atomic_dec(&rq->spin_wake);
	ASSERT(erts_smp_atomic_read(&rq->spin_wake) >= 0);
	erts_smp_atomic_dec(&rq->spin_waiter);
	ASSERT(erts_smp_atomic_read(&rq->spin_waiter) >= 0);
    }
    else {
    sleep:
	erts_smp_atomic_dec(&rq->spin_waiter);
	ASSERT(erts_smp_atomic_read(&rq->spin_waiter) >= 0);
	/*
	 * If we got new I/O tasks we aren't allowed to
	 * sleep in erl_sys_schedule().
	 */
	if (!erts_port_task_have_outstanding_io_tasks()) {
#endif

	    erts_sys_schedule_interrupt(0);
	    erts_smp_runq_unlock(rq);

	    erl_sys_schedule(0);

	    dt = do_time_read_and_reset();
	    if (dt) bump_timer(dt);

	    erts_smp_runq_lock(rq);

#if ERTS_SCHED_SLEEP_SPINCOUNT != 0
	}
    }
#endif

    sched_active_sys(no, rq);
}

static void
sched_cnd_wait(Uint no, ErtsRunQueue *rq)
{
#if ERTS_SCHED_SLEEP_SPINCOUNT != 0
    int val;
    int spincount = ERTS_SCHED_SLEEP_SPINCOUNT;
    ERTS_SMP_LC_ASSERT(erts_smp_lc_runq_is_locked(rq));
#endif

    sched_waiting(no, rq);
    erts_smp_activity_begin(ERTS_ACTIVITY_WAIT,
			    prepare_for_block,
			    resume_after_block,
			    (void *) rq);

#if ERTS_SCHED_SLEEP_SPINCOUNT == 0
    erts_smp_cnd_wait(&rq->cnd, &rq->mtx);
#else
    erts_smp_atomic_inc(&rq->spin_waiter);
    erts_smp_mtx_unlock(&rq->mtx);

    while (spincount-- > 0) {
	val = erts_smp_atomic_read(&rq->spin_wake);
	ASSERT(val >= 0);
	if (val != 0) {
	    erts_smp_mtx_lock(&rq->mtx);
	    val = erts_smp_atomic_read(&rq->spin_wake);
	    ASSERT(val >= 0);
	    if (val != 0)
		goto woken;
	    if (spincount == 0)
		goto sleep;
	    erts_smp_mtx_unlock(&rq->mtx);
	}
    }

    erts_smp_mtx_lock(&rq->mtx);
    val = erts_smp_atomic_read(&rq->spin_wake);
    ASSERT(val >= 0);
    if (val == 0) {
    sleep:
	erts_smp_atomic_dec(&rq->spin_waiter);
	ASSERT(erts_smp_atomic_read(&rq->spin_waiter) >= 0);
	erts_smp_cnd_wait(&rq->cnd, &rq->mtx);
    }
    else {
    woken:
	erts_smp_atomic_dec(&rq->spin_wake);
	ASSERT(erts_smp_atomic_read(&rq->spin_wake) >= 0);
	erts_smp_atomic_dec(&rq->spin_waiter);
	ASSERT(erts_smp_atomic_read(&rq->spin_waiter) >= 0);
    }
#endif

    erts_smp_activity_end(ERTS_ACTIVITY_WAIT,
			  prepare_for_block,
			  resume_after_block,
			  (void *) rq);

    sched_active(no, rq);
}

static void
wake_one_scheduler(void)
{
    ASSERT(erts_common_run_queue);
    ERTS_SMP_LC_ASSERT(erts_smp_lc_runq_is_locked(erts_common_run_queue));
    if (erts_common_run_queue->waiting) {
	if (!sched_spin_wake(erts_common_run_queue)) {
	    if (erts_common_run_queue->waiting == -1) /* One scheduler waiting
							 and doing so in
							 sys_schedule */
		erts_sys_schedule_interrupt(1);
	    else
		erts_smp_cnd_signal(&erts_common_run_queue->cnd);
	}
    }
}

static void
wake_scheduler(ErtsRunQueue *rq, int incq)
{
    ASSERT(!erts_common_run_queue);
    ASSERT(-1 <= rq->waiting && rq->waiting <= 1);
    ERTS_SMP_LC_ASSERT(erts_smp_lc_runq_is_locked(rq));
    if (rq->waiting && !rq->woken) {
	if (!sched_spin_wake(rq)) {
	    if (rq->waiting < 0)
		erts_sys_schedule_interrupt(1);
	    else
		erts_smp_cnd_signal(&rq->cnd);
	}
	rq->woken = 1;
	if (incq)
	    non_empty_runq(rq);
    }
}

static void
wake_all_schedulers(void)
{
    if (erts_common_run_queue) {
	erts_smp_runq_lock(erts_common_run_queue);
	if (erts_common_run_queue->waiting) {
	    if (erts_common_run_queue->waiting < 0)
		erts_sys_schedule_interrupt(1);
	    sched_spin_wake_all(erts_common_run_queue);
	    erts_smp_cnd_broadcast(&erts_common_run_queue->cnd);
	}
	erts_smp_runq_unlock(erts_common_run_queue);
    }
    else {
	int ix;
	for (ix = 0; ix < erts_no_run_queues; ix++) {
	    ErtsRunQueue *rq = ERTS_RUNQ_IX(ix);
	    erts_smp_runq_lock(rq);
	    wake_scheduler(rq, 0);
	    erts_smp_runq_unlock(rq);
	}
    }
}

static ERTS_INLINE int
chk_wake_sched(ErtsRunQueue *crq, int ix, int activate)
{
    long iflgs;
    ErtsRunQueue *wrq;
    if (crq->ix == ix)
	return 0;
    wrq = ERTS_RUNQ_IX(ix);
    iflgs = erts_smp_atomic_read(&wrq->info_flags);
    if (!(iflgs & (ERTS_RUNQ_IFLG_SUSPENDED|ERTS_RUNQ_IFLG_NONEMPTY))) {
	erts_smp_xrunq_lock(crq, wrq);
	if (activate) {
	    if (ix == erts_smp_atomic_cmpxchg(&balance_info.active_runqs, ix+1, ix)) {
		wrq->flags &= ~ERTS_RUNQ_FLG_INACTIVE;
	    }
	}
	wake_scheduler(wrq, 0);
	erts_smp_xrunq_unlock(crq, wrq);
	return 1;
    }
    return 0;
}

static void
wake_scheduler_on_empty_runq(ErtsRunQueue *crq)
{
    int ix = crq->ix;
    int stop_ix = ix;
    int active_ix = erts_smp_atomic_read(&balance_info.active_runqs);
    int balance_ix = erts_smp_atomic_read(&balance_info.used_runqs);

    if (active_ix > balance_ix)
	active_ix = balance_ix;

    if (ix >= active_ix)
	stop_ix = ix = active_ix;

    /* Try to wake a scheduler on an active run queue */
    while (1) {
	ix--;
	if (ix < 0) {
	    if (active_ix == stop_ix)
		break;
	    ix = active_ix - 1;
	}
	if (ix == stop_ix)
	    break;
	if (chk_wake_sched(crq, ix, 0))
	    return;
    }

    if (active_ix < balance_ix) {
	/* Try to activate a new run queue and wake its scheduler */
	(void) chk_wake_sched(crq, active_ix, 1);
    }
}

#endif /* ERTS_SMP */

static ERTS_INLINE void
smp_notify_inc_runq(ErtsRunQueue *runq)
{
#ifdef ERTS_SMP
    if (erts_common_run_queue)
	wake_one_scheduler();
    else
	wake_scheduler(runq, 1);
#endif
}

void
erts_smp_notify_inc_runq__(ErtsRunQueue *runq)
{
    smp_notify_inc_runq(runq);
}

#ifdef ERTS_SMP

ErtsRunQueue *
erts_prepare_emigrate(ErtsRunQueue *c_rq, ErtsRunQueueInfo *c_rqi, int prio)
{
    ASSERT(ERTS_CHK_RUNQ_FLG_EMIGRATE(c_rq->flags, prio));
    ASSERT(ERTS_CHK_RUNQ_FLG_EVACUATE(c_rq->flags, prio)
	   || c_rqi->len >= c_rqi->migrate.limit.this);

    while (1) {
	ErtsRunQueue *n_rq = c_rqi->migrate.runq;
	ERTS_DBG_VERIFY_VALID_RUNQP(n_rq);
	erts_smp_xrunq_lock(c_rq, n_rq);
	    
	/*
	 * erts_smp_xrunq_lock() may release lock on c_rq! We have
	 * to check that we still want to emigrate and emigrate
	 * to the same run queue as before.
	 */

	if (ERTS_CHK_RUNQ_FLG_EMIGRATE(c_rq->flags, prio)) {
	    Uint32 force = (ERTS_CHK_RUNQ_FLG_EVACUATE(c_rq->flags, prio)
			    | (c_rq->flags & ERTS_RUNQ_FLG_INACTIVE));
	    if (force || c_rqi->len > c_rqi->migrate.limit.this) {
		ErtsRunQueueInfo *n_rqi;
		/* We still want to emigrate */

		if (n_rq != c_rqi->migrate.runq) {
		    /* Ahh... run queue changed; need to do it all over again... */
		    erts_smp_runq_unlock(n_rq);
		    continue;
		}
		else {

		    if (prio == ERTS_PORT_PRIO_LEVEL)
			n_rqi = &n_rq->ports.info;
		    else
			n_rqi = &n_rq->procs.prio_info[prio];

		    if (force || (n_rqi->len < c_rqi->migrate.limit.other)) {
			/* emigrate ... */
			return n_rq;
		    }
		}
	    }
	}

	ASSERT(n_rq != c_rq);
	erts_smp_runq_unlock(n_rq);
	if (!(c_rq->flags & ERTS_RUNQ_FLG_INACTIVE)) {
	    /* No more emigrations to this runq */
	    ERTS_UNSET_RUNQ_FLG_EMIGRATE(c_rq->flags, prio);
	    ERTS_DBG_SET_INVALID_RUNQP(c_rqi->migrate.runq, 0x3);
	}

	return NULL;
    }
}

static void
immigrate(ErtsRunQueue *rq)
{
    int prio;

    ASSERT(rq->flags & ERTS_RUNQ_FLGS_IMMIGRATE_QMASK);

    for (prio = 0; prio < ERTS_NO_PRIO_LEVELS; prio++) {
	if (ERTS_CHK_RUNQ_FLG_IMMIGRATE(rq->flags, prio)) {
	    ErtsRunQueueInfo *rqi = (prio == ERTS_PORT_PRIO_LEVEL
				     ? &rq->ports.info
				     : &rq->procs.prio_info[prio]);
	    ErtsRunQueue *from_rq = rqi->migrate.runq;
	    int rq_locked, from_rq_locked;

	    ERTS_DBG_VERIFY_VALID_RUNQP(from_rq);

	    rq_locked = 1;
	    from_rq_locked = 1;
	    erts_smp_xrunq_lock(rq, from_rq);
	    /*
	     * erts_smp_xrunq_lock() may release lock on rq! We have
	     * to check that we still want to immigrate from the same 
	     * run queue as before.
	     */
	    if (ERTS_CHK_RUNQ_FLG_IMMIGRATE(rq->flags, prio)
		&& from_rq == rqi->migrate.runq) {
		ErtsRunQueueInfo *from_rqi = (prio == ERTS_PORT_PRIO_LEVEL
					      ? &from_rq->ports.info
					      : &from_rq->procs.prio_info[prio]);
		if ((ERTS_CHK_RUNQ_FLG_EVACUATE(rq->flags, prio)
		     && ERTS_CHK_RUNQ_FLG_EVACUATE(from_rq->flags, prio)
		     && from_rqi->len)
		    || (from_rqi->len > rqi->migrate.limit.other
			&& rqi->len < rqi->migrate.limit.this)) {
		    if (prio == ERTS_PORT_PRIO_LEVEL) {
			Port *prt = from_rq->ports.start;
			if (prt) {
			    int prt_locked = 0;
			    (void) erts_port_migrate(prt, &prt_locked,
						     from_rq, &from_rq_locked,
						     rq, &rq_locked);
			    if (prt_locked)
				erts_smp_port_unlock(prt);
			}
		    }
		    else {
			Process *proc;
			ErtsRunPrioQueue *from_rpq;
			from_rpq = (prio == PRIORITY_LOW
				    ? &from_rq->procs.prio[PRIORITY_NORMAL]
				    : &from_rq->procs.prio[prio]);
			for (proc = from_rpq->first; proc; proc = proc->next)
			    if (proc->prio == prio && !proc->bound_runq)
				break;
			if (proc) {
			    ErtsProcLocks proc_locks = 0;
			    (void) erts_proc_migrate(proc, &proc_locks,
						     from_rq, &from_rq_locked,
						     rq, &rq_locked);
			    if (proc_locks)
				erts_smp_proc_unlock(proc, proc_locks);
			}
		    }
		}
		else {
		    ERTS_UNSET_RUNQ_FLG_IMMIGRATE(rq->flags, prio);
		    ERTS_DBG_SET_INVALID_RUNQP(rqi->migrate.runq, 0x1);
		}
	    }
	    if (from_rq_locked)
		erts_smp_runq_unlock(from_rq);
	    if (!rq_locked)
		erts_smp_runq_lock(rq);
	}
    }
}

static void
evacuate_run_queue(ErtsRunQueue *evac_rq, ErtsRunQueue *rq)
{
    Port *prt;
    int prio;
    int prt_locked = 0;
    int rq_locked = 0;
    int evac_rq_locked = 1;

    erts_smp_runq_lock(evac_rq);

    evac_rq->flags &= ~ERTS_RUNQ_FLGS_IMMIGRATE_QMASK;
    evac_rq->flags |= (ERTS_RUNQ_FLGS_EMIGRATE_QMASK
		       | ERTS_RUNQ_FLGS_EVACUATE_QMASK
		       | ERTS_RUNQ_FLG_SUSPENDED);

    erts_smp_atomic_bor(&evac_rq->info_flags, ERTS_RUNQ_IFLG_SUSPENDED);

    /*
     * Need to set up evacuation paths first since we
     * may release the run queue lock on evac_rq
     * when evacuating.
     */
    evac_rq->misc.evac_runq = rq;
    evac_rq->ports.info.migrate.runq = rq;
    for (prio = 0; prio < ERTS_NO_PROC_PRIO_LEVELS; prio++)
	evac_rq->procs.prio_info[prio].migrate.runq = rq;

    /* Evacuate scheduled misc ops */

    if (evac_rq->misc.start) {
	rq_locked = 1;
	erts_smp_xrunq_lock(evac_rq, rq);
	if (rq->misc.end)
	    rq->misc.end->next = evac_rq->misc.start;
	else
	    rq->misc.start = evac_rq->misc.start;
	rq->misc.end = evac_rq->misc.end;
	evac_rq->misc.start = NULL;
	evac_rq->misc.end = NULL;
    }

    /* Evacuate scheduled ports */
    prt = evac_rq->ports.start;
    while (prt) {
	(void) erts_port_migrate(prt, &prt_locked,
				 evac_rq, &evac_rq_locked,
				 rq, &rq_locked);
	if (prt_locked)
	    erts_smp_port_unlock(prt);
	if (!evac_rq_locked) {
	    evac_rq_locked = 1;
	    erts_smp_runq_lock(evac_rq);
	}
	prt = evac_rq->ports.start;
    }

    /* Evacuate scheduled processes */
    for (prio = 0; prio < ERTS_NO_PROC_PRIO_LEVELS; prio++) {
	Process *proc;

	switch (prio) {
	case PRIORITY_MAX:
	case PRIORITY_HIGH:
	case PRIORITY_NORMAL:
	    proc = evac_rq->procs.prio[prio].first;
	    while (proc) {
		ErtsProcLocks proc_locks = 0;

		/* Bound processes are stuck... */
		while (proc->bound_runq) {
		    proc = proc->next;
		    if (!proc)
			goto end_of_proc;
		}

		(void) erts_proc_migrate(proc, &proc_locks,
					 evac_rq, &evac_rq_locked,
					 rq, &rq_locked);
		if (proc_locks)
		    erts_smp_proc_unlock(proc, proc_locks);
		if (!evac_rq_locked) {
		    erts_smp_runq_lock(evac_rq);
		    evac_rq_locked = 1;
		}

		proc = evac_rq->procs.prio[prio].first;
	    }

	end_of_proc:

#ifdef DEBUG
	    for (proc = evac_rq->procs.prio[prio].first;
		 proc;
		 proc = proc->next) {
		ASSERT(proc->bound_runq);
	    }
#endif
	    break;
	case PRIORITY_LOW:
	    break;
	default:
	    ASSERT(!"Invalid process priority");
	    break;
	}
    }

    if (rq_locked)
	erts_smp_runq_unlock(rq);

    if (!evac_rq_locked)
	erts_smp_runq_lock(evac_rq);
    wake_scheduler(evac_rq, 0);
    erts_smp_runq_unlock(evac_rq);
}

static int
try_steal_task_from_victim(ErtsRunQueue *rq, int *rq_lockedp, ErtsRunQueue *vrq)
{
    Process *proc;
    int vrq_locked;

    if (*rq_lockedp)
	erts_smp_xrunq_lock(rq, vrq);
    else
	erts_smp_runq_lock(vrq);
    vrq_locked = 1;

    ERTS_SMP_LC_CHK_RUNQ_LOCK(rq, *rq_lockedp);
    ERTS_SMP_LC_CHK_RUNQ_LOCK(vrq, vrq_locked);

    /*
     * Check for a runnable process to steal...
     */

    switch (vrq->flags & ERTS_RUNQ_FLGS_PROCS_QMASK) {
    case MAX_BIT:
    case MAX_BIT|HIGH_BIT:
    case MAX_BIT|NORMAL_BIT:
    case MAX_BIT|LOW_BIT:
    case MAX_BIT|HIGH_BIT|NORMAL_BIT:
    case MAX_BIT|HIGH_BIT|LOW_BIT:
    case MAX_BIT|NORMAL_BIT|LOW_BIT:
    case MAX_BIT|HIGH_BIT|NORMAL_BIT|LOW_BIT:
	for (proc = vrq->procs.prio[PRIORITY_MAX].last;
	     proc;
	     proc = proc->prev) {
	    if (!proc->bound_runq)
		break;
	}
	if (proc)
	    break;
    case HIGH_BIT:
    case HIGH_BIT|NORMAL_BIT:
    case HIGH_BIT|LOW_BIT:
    case HIGH_BIT|NORMAL_BIT|LOW_BIT:
	for (proc = vrq->procs.prio[PRIORITY_HIGH].last;
	     proc;
	     proc = proc->prev) {
	    if (!proc->bound_runq)
		break;
	}
	if (proc)
	    break;
    case NORMAL_BIT:
    case LOW_BIT:
    case NORMAL_BIT|LOW_BIT:
	for (proc = vrq->procs.prio[PRIORITY_NORMAL].last;
	     proc;
	     proc = proc->prev) {
	    if (!proc->bound_runq)
		break;
	}
	if (proc)
	    break;
    case 0:
	proc = NULL;
	break;
    default:
	ASSERT(!"Invalid queue mask");
	proc = NULL;
	break;
    }

    if (proc) {
	ErtsProcLocks proc_locks = 0;
	int res;
	ErtsMigrateResult mres;
	mres = erts_proc_migrate(proc, &proc_locks,
				 vrq, &vrq_locked,
				 rq, rq_lockedp);
	if (proc_locks)
	    erts_smp_proc_unlock(proc, proc_locks);
	res = !0;
	switch (mres) {
	case ERTS_MIGRATE_FAILED_RUNQ_SUSPENDED:
	    res = 0;
	case ERTS_MIGRATE_SUCCESS:
	    if (vrq_locked)
		erts_smp_runq_unlock(vrq);
	    return res;
	default: /* Other failures */
	    break;			
	}
    }

    ERTS_SMP_LC_CHK_RUNQ_LOCK(rq, *rq_lockedp);
    ERTS_SMP_LC_CHK_RUNQ_LOCK(vrq, vrq_locked);

    if (!vrq_locked) {
	if (*rq_lockedp)
	    erts_smp_xrunq_lock(rq, vrq);
	else
	    erts_smp_runq_lock(vrq);
	vrq_locked = 1;
    }

    ERTS_SMP_LC_CHK_RUNQ_LOCK(rq, *rq_lockedp);
    ERTS_SMP_LC_CHK_RUNQ_LOCK(vrq, vrq_locked);

    /*
     * Check for a runnable port to steal...
     */

    if (vrq->ports.info.len) {
	Port *prt = vrq->ports.end;
	int prt_locked = 0;
	int res;
	ErtsMigrateResult mres;

	mres = erts_port_migrate(prt, &prt_locked,
				 vrq, &vrq_locked,
				 rq, rq_lockedp);
	if (prt_locked)
	    erts_smp_port_unlock(prt);
	res = !0;
	switch (mres) {
	case ERTS_MIGRATE_FAILED_RUNQ_SUSPENDED:
	    res = 0;
	case ERTS_MIGRATE_SUCCESS:
	    if (vrq_locked)
		erts_smp_runq_unlock(vrq);
	    return res;
	default: /* Other failures */
	    break;			
	}
    }

    if (vrq_locked)
	erts_smp_runq_unlock(vrq);

    return 0;
}


static ERTS_INLINE int
check_possible_steal_victim(ErtsRunQueue *rq, int *rq_lockedp, int vix)
{
    ErtsRunQueue *vrq = ERTS_RUNQ_IX(vix);
    long iflgs = erts_smp_atomic_read(&vrq->info_flags);
    if (iflgs & ERTS_RUNQ_IFLG_NONEMPTY)
	return try_steal_task_from_victim(rq, rq_lockedp, vrq);
    else
	return 0;
}


static int
try_steal_task(ErtsRunQueue *rq)
{
    int res, rq_locked, vix, active_rqs, blnc_rqs;
    
    if (erts_common_run_queue)
	return 0;

    /*
     * We are not allowed to steal jobs to this run queue
     * if it is suspended. Note that it might get suspended
     * at any time when we don't have the lock on the run
     * queue.
     */
    if (rq->flags & ERTS_RUNQ_FLG_SUSPENDED)
	return 0;

    res = 0;
    rq_locked = 1;

    ERTS_SMP_LC_CHK_RUNQ_LOCK(rq, rq_locked);

    active_rqs = erts_smp_atomic_read(&balance_info.active_runqs);
    blnc_rqs = erts_smp_atomic_read(&balance_info.used_runqs);

    if (active_rqs > blnc_rqs)
	active_rqs = blnc_rqs;

    if (rq->ix < active_rqs) {

	/* First try to steal from an inactive run queue... */
	if (active_rqs < blnc_rqs) {
	    int no = blnc_rqs - active_rqs;
	    int stop_ix = vix = active_rqs + rq->ix % no;
	    while (erts_smp_atomic_read(&no_empty_run_queues) < blnc_rqs) {
		res = check_possible_steal_victim(rq, &rq_locked, vix);
		if (res)
		    goto done;
		vix++;
		if (vix >= blnc_rqs)
		    vix = active_rqs;
		if (vix == stop_ix)
		    break;
	    }
	}

	vix = rq->ix;

	/* ... then try to steal a job from another active queue... */
	while (erts_smp_atomic_read(&no_empty_run_queues) < blnc_rqs) {
	    vix++;
	    if (vix >= active_rqs)
		vix = 0;
	    if (vix == rq->ix)
		break;

	    res = check_possible_steal_victim(rq, &rq_locked, vix);
	    if (res)
		goto done;
	}

    }

 done:

    if (!rq_locked)
	erts_smp_runq_lock(rq);

    if (!res)
	res = !ERTS_EMPTY_RUNQ(rq);

    return res;
}

#ifdef ERTS_SMP_SCHEDULERS_NEED_TO_CHECK_CHILDREN
void
erts_smp_notify_check_children_needed(void)
{
    int i;
    for (i = 0; i < erts_no_schedulers; i++) {
	erts_smp_runq_lock(ERTS_SCHEDULER_IX(i)->run_queue);
	ERTS_SCHEDULER_IX(i)->check_children = 1;
	if (!erts_common_run_queue)
	    wake_scheduler(ERTS_SCHEDULER_IX(i)->run_queue, 0);
	erts_smp_runq_unlock(ERTS_SCHEDULER_IX(i)->run_queue);
    }
    if (ongoing_multi_scheduling_block()) {
	/* Also blocked schedulers need to check children */
	erts_smp_mtx_lock(&schdlr_sspnd.mtx);
	for (i = 0; i < erts_no_schedulers; i++)
	    ERTS_SCHEDULER_IX(i)->blocked_check_children = 1;
	erts_smp_cnd_broadcast(&schdlr_sspnd.cnd);
	erts_smp_mtx_unlock(&schdlr_sspnd.mtx);
    }
    if (erts_common_run_queue)
	wake_all_schedulers();
}
#endif

/* Run queue balancing */

typedef struct {
    Uint32 flags;
    struct {
	int max_len;
	int avail;
	int reds;
	int migration_limit;
	int emigrate_to;
	int immigrate_from;
    } prio[ERTS_NO_PRIO_LEVELS];
    int reds;
    int full_reds;
    int full_reds_history_sum;
    int full_reds_history_change;
    int oowc;
    int max_len;
} ErtsRunQueueBalance;
static ErtsRunQueueBalance *run_queue_info;

typedef struct {
    int qix;
    int len;
} ErtsRunQueueCompare;
static ErtsRunQueueCompare *run_queue_compare;

static int
rqc_len_cmp(const void *x, const void *y)
{
    return ((ErtsRunQueueCompare *) x)->len - ((ErtsRunQueueCompare *) y)->len;
}

#define ERTS_PERCENT(X, Y) \
  ((Y) == 0 \
   ? ((X) == 0 ? 100 : INT_MAX) \
   : ((100*(X))/(Y)))

#define ERTS_UPDATE_FULL_REDS(QIX, LAST_REDS)				\
do {									\
    run_queue_info[(QIX)].full_reds					\
	= run_queue_info[(QIX)].full_reds_history_sum;			\
    run_queue_info[(QIX)].full_reds += (LAST_REDS);			\
    run_queue_info[(QIX)].full_reds					\
	>>= ERTS_FULL_REDS_HISTORY_AVG_SHFT;				\
    run_queue_info[(QIX)].full_reds_history_sum				\
	-= run_queue_info[(QIX)].full_reds_history_change;		\
    run_queue_info[(QIX)].full_reds_history_sum += (LAST_REDS);		\
    run_queue_info[(QIX)].full_reds_history_change = (LAST_REDS);	\
} while (0)

#define ERTS_DBG_CHK_FULL_REDS_HISTORY(RQ)				\
do {									\
    int sum__ = 0;							\
    int rix__;								\
    for (rix__ = 0; rix__ < ERTS_FULL_REDS_HISTORY_SIZE; rix__++)	\
	sum__ += (RQ)->full_reds_history[rix__];			\
    ASSERT(sum__ == (RQ)->full_reds_history_sum);			\
} while (0);

static void
check_balance(ErtsRunQueue *c_rq)
{
    ErtsRunQueueBalance avg = {0};
    Sint64 scheds_reds, full_scheds_reds;
    int forced, active, current_active, oowc, half_full_scheds, full_scheds,
	mmax_len, blnc_no_rqs, qix, pix, freds_hist_ix;

    if (erts_smp_atomic_xchg(&balance_info.checking_balance, 1)) {
	c_rq->check_balance_reds = INT_MAX;
	return;
    }

    blnc_no_rqs = (int) erts_smp_atomic_read(&balance_info.used_runqs);
    if (blnc_no_rqs == 1) {
	c_rq->check_balance_reds = INT_MAX;
	erts_smp_atomic_set(&balance_info.checking_balance, 0);
	return;
    }

    erts_smp_runq_unlock(c_rq);

    if (balance_info.halftime) {	
	balance_info.halftime = 0;
	erts_smp_atomic_set(&balance_info.checking_balance, 0);
	ERTS_FOREACH_RUNQ(rq,
	{
	    if (rq->waiting)
		rq->flags |= ERTS_RUNQ_FLG_HALFTIME_OUT_OF_WORK;
	    else
		rq->flags &= ~ERTS_RUNQ_FLG_HALFTIME_OUT_OF_WORK;
	    rq->check_balance_reds = ERTS_RUNQ_CALL_CHECK_BALANCE_REDS;
	});

	erts_smp_runq_lock(c_rq);
	return;
    }

    /*
     * check_balance() is never called in more threads
     * than one at a time, i.e., we will normally never
     * get any conflicts on the balance_info.update_mtx.
     * However, when blocking multi scheduling (which performance
     * critical applications do *not* do) migration information
     * is manipulated. Such updates of the migration information
     * might clash with balancing.
     */
    erts_smp_mtx_lock(&balance_info.update_mtx);

    forced = balance_info.forced_check_balance;
    balance_info.forced_check_balance = 0;

    blnc_no_rqs = (int) erts_smp_atomic_read(&balance_info.used_runqs);
    if (blnc_no_rqs == 1) {
	erts_smp_mtx_unlock(&balance_info.update_mtx);
	erts_smp_runq_lock(c_rq);
	c_rq->check_balance_reds = INT_MAX;
	erts_smp_atomic_set(&balance_info.checking_balance, 0);
	return;
    }

    freds_hist_ix = balance_info.full_reds_history_index;
    balance_info.full_reds_history_index++;
    if (balance_info.full_reds_history_index >= ERTS_FULL_REDS_HISTORY_SIZE)
	balance_info.full_reds_history_index = 0;

    current_active = erts_smp_atomic_read(&balance_info.active_runqs);

    /* Read balance information for all run queues */
    for (qix = 0; qix < blnc_no_rqs; qix++) {
	ErtsRunQueue *rq = ERTS_RUNQ_IX(qix);
	erts_smp_runq_lock(rq);

	run_queue_info[qix].flags = rq->flags;
	for (pix = 0; pix < ERTS_NO_PROC_PRIO_LEVELS; pix++) {
	    run_queue_info[qix].prio[pix].max_len
		= rq->procs.prio_info[pix].max_len;
	    run_queue_info[qix].prio[pix].reds
		= rq->procs.prio_info[pix].reds;
	}
	run_queue_info[qix].prio[ERTS_PORT_PRIO_LEVEL].max_len
	    = rq->ports.info.max_len;
	run_queue_info[qix].prio[ERTS_PORT_PRIO_LEVEL].reds
	    = rq->ports.info.reds;

	run_queue_info[qix].full_reds_history_sum
	    = rq->full_reds_history_sum;
	run_queue_info[qix].full_reds_history_change
	    = rq->full_reds_history[freds_hist_ix];

	run_queue_info[qix].oowc = rq->out_of_work_count;
	run_queue_info[qix].max_len = rq->max_len;
	rq->check_balance_reds = INT_MAX;
	
	erts_smp_runq_unlock(rq);
    }

    full_scheds = 0;
    half_full_scheds = 0;
    full_scheds_reds = 0;
    scheds_reds = 0;
    oowc = 0;
    mmax_len = 0;

    /* Calculate availability for each priority in each run queues */
    for (qix = 0; qix < blnc_no_rqs; qix++) {
	int treds = 0;

	if (run_queue_info[qix].flags & ERTS_RUNQ_FLG_OUT_OF_WORK) {
	    for (pix = 0; pix < ERTS_NO_PRIO_LEVELS; pix++) {
		run_queue_info[qix].prio[pix].avail = 100;
		treds += run_queue_info[qix].prio[pix].reds;
	    }
	    if (!(run_queue_info[qix].flags & ERTS_RUNQ_FLG_HALFTIME_OUT_OF_WORK))
		half_full_scheds++;
	    ERTS_UPDATE_FULL_REDS(qix, ERTS_RUNQ_CHECK_BALANCE_REDS_PER_SCHED);
	}
	else {
	    ASSERT(!(run_queue_info[qix].flags & ERTS_RUNQ_FLG_HALFTIME_OUT_OF_WORK));
	    for (pix = 0; pix < ERTS_NO_PRIO_LEVELS; pix++)
		treds += run_queue_info[qix].prio[pix].reds;
	    if (treds == 0) {
		for (pix = 0; pix < ERTS_NO_PRIO_LEVELS; pix++)
		    run_queue_info[qix].prio[pix].avail = 0;
	    }
	    else {
		int xreds = 0;
		int procreds = treds;
		procreds -= run_queue_info[qix].prio[ERTS_PORT_PRIO_LEVEL].reds;

		for (pix = 0; pix < ERTS_NO_PROC_PRIO_LEVELS; pix++) {
		    int av;

		    if (xreds == 0)
			av = 100;
		    else if (procreds == xreds)
			av = 0;
		    else {
			av = (100*(procreds - xreds)) / procreds;
			if (av == 0)
			    av = 1;
		    }
		    run_queue_info[qix].prio[pix].avail = av;
		    if (pix < PRIORITY_NORMAL) /* ie., max or high */
			xreds += run_queue_info[qix].prio[pix].reds;
		}
		run_queue_info[qix].prio[ERTS_PORT_PRIO_LEVEL].avail = 100;
	    }
	    ERTS_UPDATE_FULL_REDS(qix, treds);
	    full_scheds_reds += run_queue_info[qix].full_reds;
	    full_scheds++;
	    half_full_scheds++;
	}
	run_queue_info[qix].reds = treds;
	scheds_reds += treds;
	oowc += run_queue_info[qix].oowc;
	if (mmax_len < run_queue_info[qix].max_len)
	    mmax_len = run_queue_info[qix].max_len;
    }

    if (!forced && half_full_scheds != blnc_no_rqs) {
	int min = 1;
	if (min < half_full_scheds)
	    min = half_full_scheds;
	if (full_scheds) {
	    active = (scheds_reds - 1)/ERTS_RUNQ_CHECK_BALANCE_REDS_PER_SCHED+1;
	}
	else {
	    active = balance_info.last_active_runqs - 1;
	}

	if (balance_info.last_active_runqs < current_active) {
	    ERTS_BLNCE_SAVE_RISE(current_active, mmax_len, scheds_reds);
	    active = current_active;
	}
	else if (active < balance_info.prev_rise.active_runqs) {
	    if (ERTS_PERCENT(mmax_len,
			     balance_info.prev_rise.max_len) >= 90
		&& ERTS_PERCENT(scheds_reds,
				balance_info.prev_rise.reds) >= 90) {
		active = balance_info.prev_rise.active_runqs;
	    }
	}

	if (active < min)
	    active = min;
	else if (active > blnc_no_rqs)
	    active = blnc_no_rqs;

	if (active == blnc_no_rqs)
	    goto all_active;

	for (qix = 0; qix < active; qix++) {
	    run_queue_info[qix].flags = 0;
	    for (pix = 0; pix < ERTS_NO_PRIO_LEVELS; pix++) {
		run_queue_info[qix].prio[pix].emigrate_to = -1;
		run_queue_info[qix].prio[pix].immigrate_from = -1;
		run_queue_info[qix].prio[pix].migration_limit = 0;
	    }
	}
	for (qix = active; qix < blnc_no_rqs; qix++) {
	    run_queue_info[qix].flags = ERTS_RUNQ_FLG_INACTIVE;
	    for (pix = 0; pix < ERTS_NO_PRIO_LEVELS; pix++) {
		int tix = qix % active;
		ERTS_SET_RUNQ_FLG_EMIGRATE(run_queue_info[qix].flags, pix);
		run_queue_info[qix].prio[pix].emigrate_to = tix;
		run_queue_info[qix].prio[pix].immigrate_from = -1;
		run_queue_info[qix].prio[pix].migration_limit = 0;
	    }
	}
    }
    else {
	if (balance_info.last_active_runqs < current_active)
	    ERTS_BLNCE_SAVE_RISE(current_active, mmax_len, scheds_reds);
    all_active:

	active = blnc_no_rqs;

	for (qix = 0; qix < blnc_no_rqs; qix++) {

	    if (full_scheds_reds > 0) {
		/* Calculate availability compared to other schedulers */
		if (!(run_queue_info[qix].flags & ERTS_RUNQ_FLG_OUT_OF_WORK)) {
		    Sint64 tmp = ((Sint64) run_queue_info[qix].full_reds
				  * (Sint64) full_scheds);
		    for (pix = 0; pix < ERTS_NO_PRIO_LEVELS; pix++) {
			Sint64 avail = run_queue_info[qix].prio[pix].avail;
			avail = (avail*tmp)/full_scheds_reds;
			ASSERT(avail >= 0);
			run_queue_info[qix].prio[pix].avail = (int) avail;
		    }
		}
	    }

	    /* Calculate average max length */
	    for (pix = 0; pix < ERTS_NO_PRIO_LEVELS; pix++) {
		run_queue_info[qix].prio[pix].emigrate_to = -1;
		run_queue_info[qix].prio[pix].immigrate_from = -1;
		avg.prio[pix].max_len += run_queue_info[qix].prio[pix].max_len;
		avg.prio[pix].avail += run_queue_info[qix].prio[pix].avail;
	    }

	}

	for (pix = 0; pix < ERTS_NO_PRIO_LEVELS; pix++) {
	    int max_len = avg.prio[pix].max_len;
	    if (max_len != 0) {
		int avail = avg.prio[pix].avail;
		if (avail != 0) {
		    max_len = ((100*max_len - 1) / avail) + 1;
		    avg.prio[pix].max_len = max_len;
		    ASSERT(max_len >= 0);
		}
	    }
	}

	/* Calculate migration limits for all priority queues in all
	   run queues */
	for (qix = 0; qix < blnc_no_rqs; qix++) {
	    run_queue_info[qix].flags = 0; /* Reset for later use... */
	    for (pix = 0; pix < ERTS_NO_PRIO_LEVELS; pix++) {
		int limit;
		if (avg.prio[pix].max_len == 0
		    || run_queue_info[qix].prio[pix].avail == 0)
		    limit = 0;
		else
		    limit = (((avg.prio[pix].max_len
			       * run_queue_info[qix].prio[pix].avail) - 1)
			     / 100 + 1);
		run_queue_info[qix].prio[pix].migration_limit = limit;
	    }
	}

	/* Setup migration paths for all priorities */
	for (pix = 0; pix < ERTS_NO_PRIO_LEVELS; pix++) {
	    int low = 0, high = 0;
	    for (qix = 0; qix < blnc_no_rqs; qix++) {
		int len_diff = run_queue_info[qix].prio[pix].max_len;
		len_diff -= run_queue_info[qix].prio[pix].migration_limit;
#ifdef DBG_PRINT
if (pix == 2) erts_fprintf(stderr, "%d ", len_diff);
#endif
		run_queue_compare[qix].qix = qix;
		run_queue_compare[qix].len = len_diff;
		if (len_diff != 0) {
		    if (len_diff < 0)
			low++;
		    else
			high++;
		}
	    }
#ifdef DBG_PRINT
if (pix == 2) erts_fprintf(stderr, "\n");
#endif
	    if (low && high) {
		int from_qix;
		int to_qix;
		int eof = 0;
		int eot = 0;
		int tix = 0;
		int fix = blnc_no_rqs-1;
		qsort(run_queue_compare,
		      blnc_no_rqs,
		      sizeof(ErtsRunQueueCompare),
		      rqc_len_cmp);

		while (1) {
		    if (run_queue_compare[fix].len <= 0)
			eof = 1;
		    if (run_queue_compare[tix].len >= 0)
			eot = 1;
		    if (eof || eot)
			break;
		    from_qix = run_queue_compare[fix].qix;
		    to_qix = run_queue_compare[tix].qix;
		    if (run_queue_info[from_qix].prio[pix].avail == 0) {
			ERTS_SET_RUNQ_FLG_EVACUATE(run_queue_info[from_qix].flags,
						   pix);
			ERTS_SET_RUNQ_FLG_EVACUATE(run_queue_info[to_qix].flags,
						   pix);
		    }
		    ERTS_SET_RUNQ_FLG_EMIGRATE(run_queue_info[from_qix].flags, pix);
		    ERTS_SET_RUNQ_FLG_IMMIGRATE(run_queue_info[to_qix].flags, pix);
		    run_queue_info[from_qix].prio[pix].emigrate_to = to_qix;
		    run_queue_info[to_qix].prio[pix].immigrate_from = from_qix;
		    tix++;
		    fix--;

#ifdef DBG_PRINT
if (pix == 2) erts_fprintf(stderr, "%d >--> %d\n", from_qix, to_qix);
#endif
		}

		if (!eot && eof) {
		    if (fix < blnc_no_rqs-1)
			fix++;

		    if (run_queue_compare[fix].len > 0) {
			int fix2 = -1;
			while (tix < fix) {
			    if (run_queue_compare[tix].len >= 0)
				break;
			    if (fix2 < fix)
				fix2 = blnc_no_rqs-1;
			    from_qix = run_queue_compare[fix2].qix;
			    to_qix = run_queue_compare[tix].qix;
			    ASSERT(to_qix != from_qix);
			    if (run_queue_info[from_qix].prio[pix].avail == 0)
				ERTS_SET_RUNQ_FLG_EVACUATE(run_queue_info[to_qix].flags,
							   pix);
			    ERTS_SET_RUNQ_FLG_IMMIGRATE(run_queue_info[to_qix].flags, pix);
			    run_queue_info[to_qix].prio[pix].immigrate_from = from_qix;
			    tix++;
			    fix2--;
#ifdef DBG_PRINT
if (pix == 2) erts_fprintf(stderr, "%d  --> %d\n", from_qix, to_qix);
#endif
			}
		    }
		}
		else if (!eof && eot) {
		    if (tix > 0)
			tix--;
		    if (run_queue_compare[tix].len < 0) {
			int tix2 = 0;
			while (tix < fix) {
			    if (run_queue_compare[fix].len <= 0)
				break;
			    if (tix2 > tix)
				tix2 = 0;
			    from_qix = run_queue_compare[fix].qix;
			    to_qix = run_queue_compare[tix2].qix;
			    ASSERT(to_qix != from_qix);
			    if (run_queue_info[from_qix].prio[pix].avail == 0)
				ERTS_SET_RUNQ_FLG_EVACUATE(run_queue_info[from_qix].flags,
							   pix);
			    ERTS_SET_RUNQ_FLG_EMIGRATE(run_queue_info[from_qix].flags, pix);
			    run_queue_info[from_qix].prio[pix].emigrate_to = to_qix;
			    fix--;
			    tix2++;
#ifdef DBG_PRINT
if (pix == 2) erts_fprintf(stderr, "%d >--  %d\n", from_qix, to_qix);
#endif

			}
		    }
		}
	    }
	}

#ifdef DBG_PRINT
erts_fprintf(stderr, "--------------------------------\n");
#endif
    }

    balance_info.last_active_runqs = active;
    erts_smp_atomic_set(&balance_info.active_runqs, active);

    balance_info.halftime = 1;
    erts_smp_atomic_set(&balance_info.checking_balance, 0);

    /* Write migration paths and reset balance statistics in all queues */
    for (qix = 0; qix < blnc_no_rqs; qix++) {
	int mqix;
	Uint32 flags;
	ErtsRunQueue *rq = ERTS_RUNQ_IX(qix);
	ErtsRunQueueInfo *rqi;
	flags = run_queue_info[qix].flags;
	erts_smp_runq_lock(rq);
	flags |= (rq->flags & ~ERTS_RUNQ_FLGS_MIGRATION_INFO);
	ASSERT(!(flags & ERTS_RUNQ_FLG_OUT_OF_WORK));
	if (rq->waiting)
	    flags |= ERTS_RUNQ_FLG_OUT_OF_WORK;

	rq->full_reds_history_sum
	    = run_queue_info[qix].full_reds_history_sum;
	rq->full_reds_history[freds_hist_ix]
	    = run_queue_info[qix].full_reds_history_change;

	ERTS_DBG_CHK_FULL_REDS_HISTORY(rq);

	rq->out_of_work_count = 0;
	rq->flags = flags;
	rq->max_len = rq->len;
	for (pix = 0; pix < ERTS_NO_PRIO_LEVELS; pix++) {
	    rqi = (pix == ERTS_PORT_PRIO_LEVEL
		   ? &rq->ports.info
		   : &rq->procs.prio_info[pix]);
	    rqi->max_len = rqi->len;
	    rqi->reds = 0;
	    if (!(ERTS_CHK_RUNQ_FLG_EMIGRATE(flags, pix)
		  | ERTS_CHK_RUNQ_FLG_IMMIGRATE(flags, pix))) {
		ASSERT(run_queue_info[qix].prio[pix].immigrate_from < 0);
		ASSERT(run_queue_info[qix].prio[pix].emigrate_to < 0);
#ifdef DEBUG
		rqi->migrate.limit.this = -1;
		rqi->migrate.limit.other = -1;
		ERTS_DBG_SET_INVALID_RUNQP(rqi->migrate.runq, 0x2);
#endif
		
	    }
	    else if (ERTS_CHK_RUNQ_FLG_EMIGRATE(flags, pix)) {
		ASSERT(!ERTS_CHK_RUNQ_FLG_IMMIGRATE(flags, pix));
		ASSERT(run_queue_info[qix].prio[pix].immigrate_from < 0);
		ASSERT(run_queue_info[qix].prio[pix].emigrate_to >= 0);

		mqix = run_queue_info[qix].prio[pix].emigrate_to;
		rqi->migrate.limit.this
		    = run_queue_info[qix].prio[pix].migration_limit;
		rqi->migrate.limit.other
		    = run_queue_info[mqix].prio[pix].migration_limit;
		rqi->migrate.runq = ERTS_RUNQ_IX(mqix);
	    }
	    else {
		ASSERT(ERTS_CHK_RUNQ_FLG_IMMIGRATE(flags, pix));
		ASSERT(run_queue_info[qix].prio[pix].emigrate_to < 0);
		ASSERT(run_queue_info[qix].prio[pix].immigrate_from >= 0);

		mqix = run_queue_info[qix].prio[pix].immigrate_from;
		rqi->migrate.limit.this
		    = run_queue_info[qix].prio[pix].migration_limit;
		rqi->migrate.limit.other
		    = run_queue_info[mqix].prio[pix].migration_limit;
		rqi->migrate.runq = ERTS_RUNQ_IX(mqix);
	    }
	}

	rq->check_balance_reds = ERTS_RUNQ_CALL_CHECK_BALANCE_REDS;
	erts_smp_runq_unlock(rq);
    }

    balance_info.n++;
    erts_smp_mtx_unlock(&balance_info.update_mtx);

    erts_smp_runq_lock(c_rq);
}

#endif /* #ifdef ERTS_SMP */

Uint
erts_debug_nbalance(void)
{
#ifdef ERTS_SMP
    Uint n;
    erts_smp_mtx_lock(&balance_info.update_mtx);
    n = balance_info.n;
    erts_smp_mtx_unlock(&balance_info.update_mtx);
    return n;
#else
    return 0;
#endif
}

void
erts_early_init_scheduling(void)
{
    early_cpu_bind_init();
}

void
erts_init_scheduling(int mrq, int no_schedulers, int no_schedulers_online)
{
    int ix, n;

#ifndef ERTS_SMP
    mrq = 0;
#endif

    init_misc_op_list_alloc();

    ASSERT(no_schedulers_online <= no_schedulers);
    ASSERT(no_schedulers_online >= 1);
    ASSERT(no_schedulers >= 1);

    /* Create and initialize run queues */

    n = (int) (mrq ? no_schedulers : 1);

    erts_aligned_run_queues = erts_alloc(ERTS_ALC_T_RUNQS,
					 (sizeof(ErtsAlignedRunQueue)*(n+1)));
    if ((((Uint) erts_aligned_run_queues) & ERTS_CACHE_LINE_MASK) == 0)
	erts_aligned_run_queues = ((ErtsAlignedRunQueue *)
				   ((((Uint) erts_aligned_run_queues)
				     & ~ERTS_CACHE_LINE_MASK)
				    + ERTS_CACHE_LINE_SIZE));

#ifdef ERTS_SMP
    erts_smp_atomic_init(&no_empty_run_queues, 0);
#endif

    for (ix = 0; ix < n; ix++) {
	int pix, rix;
	ErtsRunQueue *rq = ERTS_RUNQ_IX(ix);

	rq->ix = ix;
	erts_smp_atomic_init(&rq->info_flags, ERTS_RUNQ_IFLG_NONEMPTY);

	erts_smp_mtx_init(&rq->mtx, "run_queue");
	erts_smp_cnd_init(&rq->cnd);

	erts_smp_atomic_init(&rq->spin_waiter, 0);
	erts_smp_atomic_init(&rq->spin_wake, 0);

	rq->waiting = 0;
	rq->woken = 0;
	rq->flags = !mrq ? ERTS_RUNQ_FLG_SHARED_RUNQ : 0;
	rq->check_balance_reds = ERTS_RUNQ_CALL_CHECK_BALANCE_REDS;
	rq->full_reds_history_sum = 0;
	for (rix = 0; rix < ERTS_FULL_REDS_HISTORY_SIZE; rix++) {
	    rq->full_reds_history_sum += ERTS_RUNQ_CHECK_BALANCE_REDS_PER_SCHED;
	    rq->full_reds_history[rix] = ERTS_RUNQ_CHECK_BALANCE_REDS_PER_SCHED;
	}
	rq->out_of_work_count = 0;
	rq->max_len = 0;
	rq->len = 0;
	rq->wakeup_other = 0;
	rq->wakeup_other_reds = 0;

	rq->procs.len = 0;
	rq->procs.pending_exiters = NULL;
	rq->procs.context_switches = 0;
	rq->procs.reductions = 0;

	for (pix = 0; pix < ERTS_NO_PROC_PRIO_LEVELS; pix++) {
	    rq->procs.prio_info[pix].len = 0;
	    rq->procs.prio_info[pix].max_len = 0;
	    rq->procs.prio_info[pix].reds = 0;
	    rq->procs.prio_info[pix].migrate.limit.this = 0;
	    rq->procs.prio_info[pix].migrate.limit.other = 0;
	    ERTS_DBG_SET_INVALID_RUNQP(rq->procs.prio_info[pix].migrate.runq,
				       0x0);
	    if (pix < ERTS_NO_PROC_PRIO_LEVELS - 1) {
		rq->procs.prio[pix].first = NULL;
		rq->procs.prio[pix].last = NULL;
	    }
	}

	rq->misc.start = NULL;
	rq->misc.end = NULL;
	rq->misc.evac_runq = NULL;

	rq->ports.info.len = 0;
	rq->ports.info.max_len = 0;
	rq->ports.info.reds = 0;
	rq->ports.info.migrate.limit.this = 0;
	rq->ports.info.migrate.limit.other = 0;
	rq->ports.info.migrate.runq = NULL;
	rq->ports.start = NULL;
	rq->ports.end = NULL;
    }

    erts_common_run_queue = !mrq ? ERTS_RUNQ_IX(0) : NULL;
    erts_no_run_queues = n;

#ifdef ERTS_SMP

    if (erts_no_run_queues != 1) {
	run_queue_info = erts_alloc(ERTS_ALC_T_RUNQ_BLNS,
				    (sizeof(ErtsRunQueueBalance)
				    * erts_no_run_queues));
	run_queue_compare = erts_alloc(ERTS_ALC_T_RUNQ_BLNS,
				       (sizeof(ErtsRunQueueCompare)
					* erts_no_run_queues));
    }

#endif

    /* Create and initialize scheduler specific data */

    n = (int) no_schedulers;
    erts_aligned_scheduler_data = erts_alloc(ERTS_ALC_T_SCHDLR_DATA,
					     (sizeof(ErtsAlignedSchedulerData)
					      *(n+1)));
    if ((((Uint) erts_aligned_scheduler_data) & ERTS_CACHE_LINE_MASK) == 0)
	erts_aligned_scheduler_data = ((ErtsAlignedSchedulerData *)
				       ((((Uint) erts_aligned_scheduler_data)
					 & ~ERTS_CACHE_LINE_MASK)
					+ ERTS_CACHE_LINE_SIZE));
    for (ix = 0; ix < n; ix++) {
	ErtsSchedulerData *esdp = ERTS_SCHEDULER_IX(ix);
#ifdef ERTS_SMP
	erts_bits_init_state(&esdp->erl_bits_state);
	esdp->match_pseudo_process = NULL;
	esdp->free_process = NULL;
#endif
	esdp->no = (Uint) ix+1;
	esdp->current_process = NULL;
	esdp->current_port = NULL;

	esdp->virtual_reds = 0;
	esdp->cpu_id = -1;

	erts_init_atom_cache_map(&esdp->atom_cache_map);

	if (erts_common_run_queue) {
	    esdp->run_queue = erts_common_run_queue;
	    esdp->run_queue->scheduler = NULL;
	}
	else {
	    esdp->run_queue = ERTS_RUNQ_IX(ix);
	    esdp->run_queue->scheduler = esdp;
	}

#ifdef ERTS_SMP
#ifdef ERTS_SMP_SCHEDULERS_NEED_TO_CHECK_CHILDREN
	esdp->check_children = 0;
	esdp->blocked_check_children = 0;
#endif
	erts_smp_atomic_init(&esdp->suspended, 0);
	erts_smp_atomic_init(&esdp->chk_cpu_bind, 0);
#endif
    }

#ifdef ERTS_SMP
    erts_smp_mtx_init(&schdlr_sspnd.mtx, "schdlr_sspnd");
    erts_smp_cnd_init(&schdlr_sspnd.cnd);

    schdlr_sspnd.changing = 0;
    schdlr_sspnd.online = no_schedulers_online;
    schdlr_sspnd.curr_online = no_schedulers;
    erts_smp_atomic_init(&schdlr_sspnd.msb.ongoing, 0);
    erts_smp_atomic_init(&schdlr_sspnd.active, no_schedulers);
    schdlr_sspnd.msb.procs = NULL;
    erts_smp_atomic_set(&balance_info.used_runqs,
			erts_common_run_queue ? 1 : no_schedulers_online);
    erts_smp_atomic_init(&balance_info.active_runqs, no_schedulers);
    balance_info.last_active_runqs = no_schedulers;
    erts_smp_mtx_init(&balance_info.update_mtx, "migration_info_update");
    balance_info.forced_check_balance = 0;
    balance_info.halftime = 1;
    balance_info.full_reds_history_index = 0;
    erts_smp_atomic_init(&balance_info.checking_balance, 0);
    balance_info.prev_rise.active_runqs = 0;
    balance_info.prev_rise.max_len = 0;
    balance_info.prev_rise.reds = 0;
    balance_info.n = 0;

    if (no_schedulers_online < no_schedulers) {
	if (erts_common_run_queue) {
	    for (ix = no_schedulers_online; ix < no_schedulers; ix++)
		erts_smp_atomic_set(&(ERTS_SCHEDULER_IX(ix)->suspended), 1);
	}
	else {
	    for (ix = no_schedulers_online; ix < erts_no_run_queues; ix++)
		evacuate_run_queue(ERTS_RUNQ_IX(ix),
				   ERTS_RUNQ_IX(ix % no_schedulers_online));
	}
    }

    schdlr_sspnd.wait_curr_online = no_schedulers_online;
    schdlr_sspnd.curr_online *= 2; /* Boot strapping... */
    schdlr_sspnd.changing = ERTS_SCHED_CHANGING_ONLINE;

    erts_smp_atomic_init(&doing_sys_schedule, 0);

#else /* !ERTS_SMP */
    {
	ErtsSchedulerData *esdp;
	esdp = ERTS_SCHEDULER_IX(0);
	erts_scheduler_data = esdp;
#ifdef USE_THREADS
	erts_tsd_set(sched_data_key, (void *) esdp);
#endif
    }
    erts_no_schedulers = 1;
#endif

    erts_smp_atomic_init(&function_calls, 0);

    /* init port tasks */
    erts_port_task_init();

    late_cpu_bind_init();
}

ErtsRunQueue *
erts_schedid2runq(Uint id)
{
    int ix;
    if (erts_common_run_queue)
	return erts_common_run_queue;
    ix = (int) id - 1;
    ASSERT(0 <= ix && ix < erts_no_run_queues);
    return ERTS_RUNQ_IX(ix);
}

#ifdef USE_THREADS

ErtsSchedulerData *
erts_get_scheduler_data(void)
{
    return (ErtsSchedulerData *) erts_tsd_get(sched_data_key);
}

#endif

static int remove_proc_from_runq(ErtsRunQueue *rq, Process *p, int to_inactive);

static ERTS_INLINE void
suspend_process(ErtsRunQueue *rq, Process *p)
{
    ERTS_SMP_LC_ASSERT(ERTS_PROC_LOCK_STATUS & erts_proc_lc_my_proc_locks(p));
    ERTS_SMP_LC_ASSERT(erts_smp_lc_runq_is_locked(rq));
    p->rcount++;  /* count number of suspend */
#ifdef ERTS_SMP
    ASSERT(!(p->runq_flags & ERTS_PROC_RUNQ_FLG_RUNNING)
	   || p == erts_get_current_process());
    ASSERT(p->status != P_RUNNING
	   || p->runq_flags & ERTS_PROC_RUNQ_FLG_RUNNING);
    if (p->status_flags & ERTS_PROC_SFLG_PENDADD2SCHEDQ)
	goto runable;
#endif
    switch(p->status) {
    case P_SUSPENDED:
	break;
    case P_RUNABLE:
#ifdef ERTS_SMP
    runable:
        if (!ERTS_PROC_PENDING_EXIT(p)) 
#endif
	    remove_proc_from_runq(rq, p, 1);
	/* else:
	 * leave process in schedq so it will discover the pending exit
	 */
	p->rstatus = P_RUNABLE; /* wakeup as runnable */
	break;
    case P_RUNNING:
	p->rstatus = P_RUNABLE; /* wakeup as runnable */
	break;
    case P_WAITING:
	p->rstatus = P_WAITING; /* wakeup as waiting */
	break;
    case P_EXITING:
	return; /* ignore this */
    case P_GARBING:
    case P_FREE:
	erl_exit(1, "bad state in suspend_process()\n");
    }

    if ((erts_system_profile_flags.runnable_procs) && (p->rcount == 1) && (p->status != P_WAITING)) {
        profile_runnable_proc(p, am_inactive);
    }

    p->status = P_SUSPENDED;
    
}

static ERTS_INLINE void
resume_process(Process *p)
{
    Uint32 *statusp;
    ERTS_SMP_LC_ASSERT(ERTS_PROC_LOCK_STATUS & erts_proc_lc_my_proc_locks(p));
    switch (p->status) {
    case P_SUSPENDED:
	statusp = &p->status;
	break;
    case P_GARBING:
	if (p->gcstatus == P_SUSPENDED) {
	    statusp = &p->gcstatus;
	    break;
	}
	/* Fall through */
    default:
	return;
    }

    ASSERT(p->rcount > 0);

    if (--p->rcount > 0)  /* multiple suspend */
	return;
    switch(p->rstatus) {
    case P_RUNABLE:
	*statusp = P_WAITING;  /* make erts_add_to_runq work */
	erts_add_to_runq(p);
	break;
    case P_WAITING:
	*statusp = P_WAITING;
	break;
    default:
	erl_exit(1, "bad state in resume_process()\n");
    }
    p->rstatus = P_FREE;    
}

#ifdef ERTS_SMP

static void
susp_sched_prep_block(void *unused)
{
    erts_smp_mtx_unlock(&schdlr_sspnd.mtx);
}

static void
susp_sched_resume_block(void *unused)
{
    erts_smp_mtx_lock(&schdlr_sspnd.mtx);
}

static void
suspend_scheduler(ErtsSchedulerData *esdp)
{
    long no = (long) esdp->no;
    ErtsRunQueue *rq = esdp->run_queue;
    long active_schedulers;
    int curr_online = 1;
    int wake = 0;

    /*
     * Schedulers may be suspended in two different ways:
     * - A scheduler may be suspended since it is not online.
     *   All schedulers with scheduler ids greater than
     *   schdlr_sspnd.online are suspended.
     * - Multi scheduling is blocked. All schedulers except the
     *   scheduler with scheduler id 1 are suspended.
     *
     * Regardless of why a scheduler is suspended, it ends up here.
     */

    ASSERT(no != 1);

    erts_smp_runq_unlock(esdp->run_queue);

    /* Unbind from cpu */
    erts_smp_rwmtx_rwlock(&erts_cpu_bind_rwmtx);
    if (scheduler2cpu_map[esdp->no].bound_id >= 0
	&& erts_unbind_from_cpu(erts_cpuinfo) == 0) {
	esdp->cpu_id = scheduler2cpu_map[esdp->no].bound_id = -1;
    }
    erts_smp_rwmtx_rwunlock(&erts_cpu_bind_rwmtx);

    if (erts_system_profile_flags.scheduler)
    	profile_scheduler(make_small(esdp->no), am_inactive);

    erts_smp_mtx_lock(&schdlr_sspnd.mtx);

    active_schedulers = erts_smp_atomic_dectest(&schdlr_sspnd.active);
    ASSERT(active_schedulers >= 1);
    if (schdlr_sspnd.changing == ERTS_SCHED_CHANGING_MULTI_SCHED) {
	if (active_schedulers == schdlr_sspnd.msb.wait_active)
	    wake = 1;
	if (active_schedulers == 1)
	    schdlr_sspnd.changing = 0;
    }

    while (1) {

#ifdef ERTS_SMP_SCHEDULERS_NEED_TO_CHECK_CHILDREN
	int check_children;
	erts_smp_runq_lock(esdp->run_queue);
	check_children = esdp->check_children;
	esdp->check_children = 0;
	erts_smp_runq_unlock(esdp->run_queue);
	if (check_children) {
	    erts_smp_mtx_unlock(&schdlr_sspnd.mtx);
	    erts_check_children();
	    erts_smp_mtx_lock(&schdlr_sspnd.mtx);
	}
#endif

	if (schdlr_sspnd.changing == ERTS_SCHED_CHANGING_ONLINE) {
	    int changed = 0;
	    if (no > schdlr_sspnd.online && curr_online) {
		schdlr_sspnd.curr_online--;
		curr_online = 0;
		changed = 1;
	    }
	    else if (no <= schdlr_sspnd.online && !curr_online) {
		schdlr_sspnd.curr_online++;
		curr_online = 1;
		changed = 1;
	    }
	    if (changed
		&& schdlr_sspnd.curr_online == schdlr_sspnd.wait_curr_online)
		wake = 1;
	    if (schdlr_sspnd.online == schdlr_sspnd.curr_online)
		schdlr_sspnd.changing = 0;
	}

	if (wake) {
	    erts_smp_cnd_broadcast(&schdlr_sspnd.cnd);
	    wake = 0;
	}


	if (!(rq->flags & (ERTS_RUNQ_FLG_SHARED_RUNQ|ERTS_RUNQ_FLG_SUSPENDED)))
	    break;
	if ((rq->flags & ERTS_RUNQ_FLG_SHARED_RUNQ)
	    && !erts_smp_atomic_read(&esdp->suspended))
	    break;

	erts_smp_activity_begin(ERTS_ACTIVITY_WAIT,
				susp_sched_prep_block,
				susp_sched_resume_block,
				NULL);
	while (1) {

#ifdef ERTS_SMP_SCHEDULERS_NEED_TO_CHECK_CHILDREN
	    if (esdp->blocked_check_children)
		break;
#endif

	    erts_smp_cnd_wait(&schdlr_sspnd.cnd, &schdlr_sspnd.mtx);

	    if (schdlr_sspnd.changing == ERTS_SCHED_CHANGING_ONLINE)
		break;

	    if (!(rq->flags & (ERTS_RUNQ_FLG_SHARED_RUNQ
			       | ERTS_RUNQ_FLG_SUSPENDED)))
		break;
	    if ((rq->flags & ERTS_RUNQ_FLG_SHARED_RUNQ)
		&& !erts_smp_atomic_read(&esdp->suspended))
		break;
	}

#ifdef ERTS_SMP_SCHEDULERS_NEED_TO_CHECK_CHILDREN
	esdp->blocked_check_children = 0;
#endif

	erts_smp_activity_end(ERTS_ACTIVITY_WAIT,
			      susp_sched_prep_block,
			      susp_sched_resume_block,
			      NULL);
    }

    erts_smp_atomic_inc(&schdlr_sspnd.active);

    erts_smp_mtx_unlock(&schdlr_sspnd.mtx);

    if (erts_system_profile_flags.scheduler)
    	profile_scheduler(make_small(esdp->no), am_active);

    erts_smp_runq_lock(esdp->run_queue);
    non_empty_runq(esdp->run_queue);

    /* Make sure we check if we should bind to a cpu or not... */
    if (rq->flags & ERTS_RUNQ_FLG_SHARED_RUNQ)
	erts_smp_atomic_set(&esdp->chk_cpu_bind, 1);
    else
	rq->flags |= ERTS_RUNQ_FLG_CHK_CPU_BIND;
}

#define ERTS_RUNQ_RESET_SUSPEND_INFO(RQ, DBG_ID)			\
do {									\
    int pix__;								\
    (RQ)->misc.evac_runq = NULL;					\
    (RQ)->ports.info.migrate.runq = NULL;				\
    (RQ)->flags &= ~(ERTS_RUNQ_FLGS_IMMIGRATE_QMASK			\
		     | ERTS_RUNQ_FLGS_EMIGRATE_QMASK			\
		     | ERTS_RUNQ_FLGS_EVACUATE_QMASK			\
		     | ERTS_RUNQ_FLG_SUSPENDED);			\
    (RQ)->flags |= (ERTS_RUNQ_FLG_OUT_OF_WORK				\
                    | ERTS_RUNQ_FLG_HALFTIME_OUT_OF_WORK);		\
    (RQ)->check_balance_reds = ERTS_RUNQ_CALL_CHECK_BALANCE_REDS;	\
    erts_smp_atomic_band(&(RQ)->info_flags, ~ERTS_RUNQ_IFLG_SUSPENDED);	\
    for (pix__ = 0; pix__ < ERTS_NO_PROC_PRIO_LEVELS; pix__++) {	\
	(RQ)->procs.prio_info[pix__].max_len = 0;			\
	(RQ)->procs.prio_info[pix__].reds = 0;				\
	ERTS_DBG_SET_INVALID_RUNQP((RQ)->procs.prio_info[pix__].migrate.runq,\
				   (DBG_ID));				\
    }									\
    (RQ)->ports.info.max_len = 0;					\
    (RQ)->ports.info.reds = 0;						\
} while (0)

#define ERTS_RUNQ_RESET_MIGRATION_PATHS__(RQ)				\
do {									\
    ERTS_SMP_LC_ASSERT(erts_smp_lc_runq_is_locked((RQ)));		\
    (RQ)->misc.evac_runq = NULL;					\
    (RQ)->ports.info.migrate.runq = NULL;				\
    (RQ)->flags &= ~(ERTS_RUNQ_FLGS_IMMIGRATE_QMASK			\
		     | ERTS_RUNQ_FLGS_EMIGRATE_QMASK			\
		     | ERTS_RUNQ_FLGS_EVACUATE_QMASK);			\
} while (0)

#ifdef DEBUG
#define ERTS_RUNQ_RESET_MIGRATION_PATHS(RQ, DBG_ID)			\
do {									\
    int pix__;								\
    ERTS_RUNQ_RESET_MIGRATION_PATHS__((RQ));				\
    for (pix__ = 0; pix__ < ERTS_NO_PROC_PRIO_LEVELS; pix__++)		\
	ERTS_DBG_SET_INVALID_RUNQP((RQ)->procs.prio_info[pix__].migrate.runq,\
				   (DBG_ID));				\
} while (0)
#else
#define ERTS_RUNQ_RESET_MIGRATION_PATHS(RQ, DBG_ID) \
  ERTS_RUNQ_RESET_MIGRATION_PATHS__((RQ))
#endif

ErtsSchedSuspendResult
erts_schedulers_state(Uint *total,
		      Uint *online,
		      Uint *active,
		      int yield_allowed)
{
    int res;
    erts_smp_mtx_lock(&schdlr_sspnd.mtx);
    if (yield_allowed && schdlr_sspnd.changing)
	res = ERTS_SCHDLR_SSPND_YIELD_RESTART;
    else {
	*active = *online = schdlr_sspnd.online;
	if (ongoing_multi_scheduling_block())
	    *active = 1;
	res = ERTS_SCHDLR_SSPND_DONE;
    }
    erts_smp_mtx_unlock(&schdlr_sspnd.mtx);
    *total = erts_no_schedulers;
    return res;
}

ErtsSchedSuspendResult
erts_set_schedulers_online(Process *p,
			   ErtsProcLocks plocks,
			   Sint new_no,
			   Sint *old_no)
{
    int ix, res, no, have_unlocked_plocks;

    if (new_no < 1 || erts_no_schedulers < new_no)
	return ERTS_SCHDLR_SSPND_EINVAL;

    erts_smp_mtx_lock(&schdlr_sspnd.mtx);

    have_unlocked_plocks = 0;
    no = (int) new_no;

    if (schdlr_sspnd.changing) {
	res = ERTS_SCHDLR_SSPND_YIELD_RESTART;
    }
    else {
	int online = *old_no = schdlr_sspnd.online;
	if (no == schdlr_sspnd.online) {
	    res = ERTS_SCHDLR_SSPND_DONE;
	}
	else {
	    schdlr_sspnd.changing = ERTS_SCHED_CHANGING_ONLINE;
	    schdlr_sspnd.online = no;
	    if (no > online) {
		int ix;
		schdlr_sspnd.wait_curr_online = no;
		if (ongoing_multi_scheduling_block())
		    /* No schedulers to resume */;
		else if (erts_common_run_queue) {
		    for (ix = online; ix < no; ix++)
			erts_smp_atomic_set(&ERTS_SCHEDULER_IX(ix)->suspended,
					    0);
		}
		else {
		    if (plocks) {
			have_unlocked_plocks = 1;
			erts_smp_proc_unlock(p, plocks);
		    }
		    erts_smp_mtx_unlock(&schdlr_sspnd.mtx);
		    erts_smp_mtx_lock(&balance_info.update_mtx);
		    for (ix = online; ix < no; ix++) {
			ErtsRunQueue *rq = ERTS_RUNQ_IX(ix);
			erts_smp_runq_lock(rq);
			ERTS_RUNQ_RESET_SUSPEND_INFO(rq, 0x5);
			erts_smp_runq_unlock(rq);
		    }
		    /*
		     * Spread evacuation paths among all online
		     * run queues.
		     */
		    for (ix = no; ix < erts_no_run_queues; ix++) {
			ErtsRunQueue *from_rq = ERTS_RUNQ_IX(ix);
			ErtsRunQueue *to_rq = ERTS_RUNQ_IX(ix % no);
			evacuate_run_queue(from_rq, to_rq);
		    }
		    erts_smp_atomic_set(&balance_info.used_runqs, no);
		    erts_smp_mtx_unlock(&balance_info.update_mtx);
		    erts_smp_mtx_lock(&schdlr_sspnd.mtx);
		}
		erts_smp_cnd_broadcast(&schdlr_sspnd.cnd);
		res = ERTS_SCHDLR_SSPND_DONE;
	    }
	    else /* if (no < online) */ {
		if (p->scheduler_data->no <= no) {
		    res = ERTS_SCHDLR_SSPND_DONE;
		    schdlr_sspnd.wait_curr_online = no;
		}
		else {
		    /*
		     * Yield! Current process needs to migrate
		     * before bif returns.
		     */
		    res = ERTS_SCHDLR_SSPND_YIELD_DONE;
		    schdlr_sspnd.wait_curr_online = no+1;
		}

		if (ongoing_multi_scheduling_block())
		    erts_smp_cnd_broadcast(&schdlr_sspnd.cnd);
		else if (erts_common_run_queue) {
		    for (ix = no; ix < online; ix++)
			erts_smp_atomic_set(&ERTS_SCHEDULER_IX(ix)->suspended,
					    1);
		    wake_all_schedulers();
		}
		else {
		    if (plocks) {
			have_unlocked_plocks = 1;
			erts_smp_proc_unlock(p, plocks);
		    }
		    erts_smp_mtx_unlock(&schdlr_sspnd.mtx);
		    erts_smp_mtx_lock(&balance_info.update_mtx);
		    
		    for (ix = 0; ix < online; ix++) {
			ErtsRunQueue *rq = ERTS_RUNQ_IX(ix);
			erts_smp_runq_lock(rq);
			ERTS_RUNQ_RESET_MIGRATION_PATHS(rq, 0x6);
			erts_smp_runq_unlock(rq);
		    }
		    /*
		     * Evacutation order important! Newly suspended run queues
		     * has to be evacuated last.
		     */
		    for (ix = erts_no_run_queues-1; ix >= no; ix--)
			evacuate_run_queue(ERTS_RUNQ_IX(ix),
					   ERTS_RUNQ_IX(ix % no));
		    erts_smp_atomic_set(&balance_info.used_runqs, no);
		    erts_smp_mtx_unlock(&balance_info.update_mtx);
		    erts_smp_mtx_lock(&schdlr_sspnd.mtx);
		    ERTS_FOREACH_OP_RUNQ(rq, wake_scheduler(rq, 0));
		}
	    }

	    erts_smp_activity_begin(ERTS_ACTIVITY_WAIT,
				    susp_sched_prep_block,
				    susp_sched_resume_block,
				    NULL);
	    while (schdlr_sspnd.curr_online != schdlr_sspnd.wait_curr_online)
		erts_smp_cnd_wait(&schdlr_sspnd.cnd, &schdlr_sspnd.mtx);
	    erts_smp_activity_end(ERTS_ACTIVITY_WAIT,
				  susp_sched_prep_block,
				  susp_sched_resume_block,
				  NULL);
	}
    }

    erts_smp_mtx_unlock(&schdlr_sspnd.mtx);
    if (have_unlocked_plocks)
	erts_smp_proc_lock(p, plocks);

    return res;
}

ErtsSchedSuspendResult
erts_block_multi_scheduling(Process *p, ErtsProcLocks plocks, int on, int all)
{
    int ix, res, have_unlocked_plocks = 0;
    ErtsProcList *plp;

    erts_smp_mtx_lock(&schdlr_sspnd.mtx);
    if (on) {
	if (schdlr_sspnd.changing) {
	    res = ERTS_SCHDLR_SSPND_YIELD_RESTART; /* Yield */
	}
	else if (erts_is_multi_scheduling_blocked()) {
	    plp = proclist_create(p);
	    plp->next = schdlr_sspnd.msb.procs;
	    schdlr_sspnd.msb.procs = plp;
	    p->flags |= F_HAVE_BLCKD_MSCHED;
	    ASSERT(erts_smp_atomic_read(&schdlr_sspnd.active) == 1);
	    ASSERT(p->scheduler_data->no == 1);
	    res = 1;
	}
	else {
	    p->flags |= F_HAVE_BLCKD_MSCHED;
	    if (plocks) {
		have_unlocked_plocks = 1;
		erts_smp_proc_unlock(p, plocks);
	    }
	    erts_smp_atomic_set(&schdlr_sspnd.msb.ongoing, 1);
	    if (schdlr_sspnd.online == 1) {
		res = ERTS_SCHDLR_SSPND_DONE_MSCHED_BLOCKED;
		ASSERT(erts_smp_atomic_read(&schdlr_sspnd.active) == 1);
		ASSERT(p->scheduler_data->no == 1);
	    }
	    else {
		schdlr_sspnd.changing = ERTS_SCHED_CHANGING_MULTI_SCHED;
		if (p->scheduler_data->no == 1) {
		    res = ERTS_SCHDLR_SSPND_DONE_MSCHED_BLOCKED;
		    schdlr_sspnd.msb.wait_active = 1;
		}
		else {
		    /*
		     * Yield! Current process needs to migrate
		     * before bif returns.
		     */
		    res = ERTS_SCHDLR_SSPND_YIELD_DONE_MSCHED_BLOCKED;
		    schdlr_sspnd.msb.wait_active = 2;
		}
		if (erts_common_run_queue) {
		    for (ix = 1; ix < schdlr_sspnd.online; ix++)
			erts_smp_atomic_set(&ERTS_SCHEDULER_IX(ix)->suspended, 1);
		    wake_all_schedulers();
		}
		else {
		    erts_smp_mtx_unlock(&schdlr_sspnd.mtx);
		    erts_smp_mtx_lock(&balance_info.update_mtx);
		    erts_smp_atomic_set(&balance_info.used_runqs, 1);
		    for (ix = 0; ix < schdlr_sspnd.online; ix++) {
			ErtsRunQueue *rq = ERTS_RUNQ_IX(ix);
			erts_smp_runq_lock(rq);
			ERTS_RUNQ_RESET_MIGRATION_PATHS(rq, 0x7);
			erts_smp_runq_unlock(rq);
		    }
		    /*
		     * Evacuate all activities in all other run queues
		     * into the first run queue. Note order is important,
		     * online run queues has to be evacuated last.
		     */
		    for (ix = erts_no_run_queues-1; ix >= 1; ix--)
			evacuate_run_queue(ERTS_RUNQ_IX(ix), ERTS_RUNQ_IX(0));
		    erts_smp_mtx_unlock(&balance_info.update_mtx);
		    erts_smp_mtx_lock(&schdlr_sspnd.mtx);
		}
		erts_smp_activity_begin(ERTS_ACTIVITY_WAIT,
					susp_sched_prep_block,
					susp_sched_resume_block,
					NULL);
		while (erts_smp_atomic_read(&schdlr_sspnd.active)
		       != schdlr_sspnd.msb.wait_active)
		    erts_smp_cnd_wait(&schdlr_sspnd.cnd, &schdlr_sspnd.mtx);
		erts_smp_activity_end(ERTS_ACTIVITY_WAIT,
				      susp_sched_prep_block,
				      susp_sched_resume_block,
				      NULL);
	    }
	    plp = proclist_create(p);
	    plp->next = schdlr_sspnd.msb.procs;
	    schdlr_sspnd.msb.procs = plp;
#ifdef DEBUG
	    ERTS_FOREACH_RUNQ(srq,
	    {
		if (srq != ERTS_RUNQ_IX(0)) {
		    ASSERT(ERTS_EMPTY_RUNQ(srq));
		    ASSERT(srq->flags & ERTS_RUNQ_FLG_SUSPENDED);
		}
	    });
#endif
	    ASSERT(p->scheduler_data);
	}
    }
    else if (!ongoing_multi_scheduling_block()) {
	ASSERT(!schdlr_sspnd.msb.procs);
	res = ERTS_SCHDLR_SSPND_DONE;
    }
    else {
	if (p->flags & F_HAVE_BLCKD_MSCHED) {
	    ErtsProcList **plpp = &schdlr_sspnd.msb.procs;
	    plp = schdlr_sspnd.msb.procs;

	    while (plp) {
		if (!proclist_same(plp, p)){
		    plpp = &plp->next;
		    plp = plp->next;
		}
		else {
		    *plpp = plp->next;
		    proclist_destroy(plp);
		    if (!all)
			break;
		    plp = *plpp;
		}
	    }
	}
	if (schdlr_sspnd.msb.procs)
	    res = ERTS_SCHDLR_SSPND_DONE_MSCHED_BLOCKED;
	else {
	    schdlr_sspnd.changing = ERTS_SCHED_CHANGING_MULTI_SCHED;
#ifdef DEBUG
	    ERTS_FOREACH_RUNQ(rq,
	    {
		if (rq != p->scheduler_data->run_queue) {
		    if (!ERTS_EMPTY_RUNQ(rq)) {
			Process *rp;
			int pix;
			ASSERT(rq->ports.info.len == 0);
			for (pix = 0; pix < ERTS_NO_PROC_PRIO_LEVELS; pix++) {
			    for (rp = rq->procs.prio[pix].first;
				 rp;
				 rp = rp->next) {
				ASSERT(rp->bound_runq);
			    }
			}
		    }

		    ASSERT(rq->flags & ERTS_RUNQ_FLG_SUSPENDED);
		}
	    });
#endif
	    p->flags &= ~F_HAVE_BLCKD_MSCHED;
	    erts_smp_atomic_set(&schdlr_sspnd.msb.ongoing, 0);
	    if (schdlr_sspnd.online == 1)
		/* No schedulers to resume */;
	    else if (erts_common_run_queue) {
		for (ix = 1; ix < schdlr_sspnd.online; ix++)
		    erts_smp_atomic_set(&ERTS_SCHEDULER_IX(ix)->suspended, 0);
		wake_all_schedulers();
	    }
	    else {
		int online = schdlr_sspnd.online;
		erts_smp_mtx_unlock(&schdlr_sspnd.mtx);
		if (plocks) {
		    have_unlocked_plocks = 1;
		    erts_smp_proc_unlock(p, plocks);
		}
		erts_smp_mtx_lock(&balance_info.update_mtx);

		/* Resume all online run queues */
		for (ix = 1; ix < online; ix++) {
		    ErtsRunQueue *rq = ERTS_RUNQ_IX(ix);
		    erts_smp_runq_lock(rq);
		    ERTS_RUNQ_RESET_SUSPEND_INFO(rq, 0x4);
		    erts_smp_runq_unlock(rq);
		}

		/* Spread evacuation paths among all online run queues */
		for (ix = online; ix < erts_no_run_queues; ix++)
		    evacuate_run_queue(ERTS_RUNQ_IX(ix),
				       ERTS_RUNQ_IX(ix % online));

		erts_smp_atomic_set(&balance_info.used_runqs, online);
		/* Make sure that we balance soon... */
		balance_info.forced_check_balance = 1;
		erts_smp_runq_lock(ERTS_RUNQ_IX(0));
		ERTS_RUNQ_IX(0)->check_balance_reds = 0;
		erts_smp_runq_unlock(ERTS_RUNQ_IX(0));
		erts_smp_mtx_unlock(&balance_info.update_mtx);
		erts_smp_mtx_lock(&schdlr_sspnd.mtx);
	    }
	    erts_smp_cnd_broadcast(&schdlr_sspnd.cnd);
	    schdlr_sspnd.changing = 0;
	    res = ERTS_SCHDLR_SSPND_DONE;
	}
    }

    erts_smp_mtx_unlock(&schdlr_sspnd.mtx);
    if (have_unlocked_plocks)
	erts_smp_proc_lock(p, plocks);
    return res;
}

#ifdef DEBUG
void
erts_dbg_multi_scheduling_return_trap(Process *p, Eterm return_value)
{
    if (return_value == am_blocked) {
	long active = erts_smp_atomic_read(&schdlr_sspnd.active);
	ASSERT(1 <= active && active <= 2);
	ASSERT(ERTS_PROC_GET_SCHDATA(p)->no == 1);
    }
}
#endif

int
erts_is_multi_scheduling_blocked(void)
{
    return (erts_smp_atomic_read(&schdlr_sspnd.msb.ongoing)
	    && erts_smp_atomic_read(&schdlr_sspnd.active) == 1);
}

Eterm
erts_multi_scheduling_blockers(Process *p)
{
    Eterm res = NIL;

    erts_smp_mtx_lock(&schdlr_sspnd.mtx);
    if (erts_is_multi_scheduling_blocked()) {
	Eterm *hp, *hp_end;
	ErtsProcList *plp1, *plp2;
	Uint max_size;
	ASSERT(schdlr_sspnd.msb.procs);
	for (max_size = 0, plp1 = schdlr_sspnd.msb.procs;
	     plp1;
	     plp1 = plp1->next) {
	    max_size += 2;
	}
	ASSERT(max_size);
	hp = HAlloc(p, max_size);
	hp_end = hp + max_size;
	for (plp1 = schdlr_sspnd.msb.procs; plp1; plp1 = plp1->next) {
	    for (plp2 = schdlr_sspnd.msb.procs;
		 plp2->pid != plp1->pid;
		 plp2 = plp2->next);
	    if (plp2 == plp1) {
		res = CONS(hp, plp1->pid, res);
		hp += 2;
	    }
	    /* else: already in result list */
	}
	HRelease(p, hp_end, hp);
    }
    erts_smp_mtx_unlock(&schdlr_sspnd.mtx);
    return res;
}

static void *
sched_thread_func(void *vesdp)
{
#ifdef ERTS_ENABLE_LOCK_CHECK
    {
	char buf[31];
	Uint no = ((ErtsSchedulerData *) vesdp)->no;
	erts_snprintf(&buf[0], 31, "scheduler %bpu", no);
	erts_lc_set_thread_name(&buf[0]);
    }
#endif
    erts_alloc_reg_scheduler_id(((ErtsSchedulerData *) vesdp)->no);
    erts_tsd_set(sched_data_key, vesdp);
#ifdef ERTS_SMP
    erts_proc_lock_prepare_proc_lock_waiter();
#endif
    erts_register_blockable_thread();
#ifdef HIPE
    hipe_thread_signal_init();
#endif
    erts_thread_init_float();
    erts_smp_mtx_lock(&schdlr_sspnd.mtx);

    ASSERT(schdlr_sspnd.changing == ERTS_SCHED_CHANGING_ONLINE);

    schdlr_sspnd.curr_online--;

    if (((ErtsSchedulerData *) vesdp)->no != 1) {
	if (schdlr_sspnd.online == schdlr_sspnd.curr_online) {
	    schdlr_sspnd.changing = 0;
	    erts_smp_cnd_broadcast(&schdlr_sspnd.cnd);
	}
    }
    else if (schdlr_sspnd.curr_online == schdlr_sspnd.wait_curr_online)
	schdlr_sspnd.changing = 0;
    else {
	erts_smp_activity_begin(ERTS_ACTIVITY_WAIT,
				susp_sched_prep_block,
				susp_sched_resume_block,
				NULL);
	while (schdlr_sspnd.curr_online != schdlr_sspnd.wait_curr_online)
	    erts_smp_cnd_wait(&schdlr_sspnd.cnd, &schdlr_sspnd.mtx);
	erts_smp_activity_end(ERTS_ACTIVITY_WAIT,
			      susp_sched_prep_block,
			      susp_sched_resume_block,
			      NULL);
	ASSERT(!schdlr_sspnd.changing);
    }
    erts_smp_mtx_unlock(&schdlr_sspnd.mtx);

    process_main();
    /* No schedulers should *ever* terminate */
    erl_exit(ERTS_ABORT_EXIT, "Scheduler thread number %bpu terminated\n",
	     ((ErtsSchedulerData *) vesdp)->no);
    return NULL;
}

void
erts_start_schedulers(void)
{
    int res = 0;
    Uint actual = 0;
    Uint wanted = erts_no_schedulers;
    Uint wanted_no_schedulers = erts_no_schedulers;
    ethr_thr_opts opts = ETHR_THR_OPTS_DEFAULT_INITER;

    opts.detached = 1;
    opts.suggested_stack_size = erts_sched_thread_suggested_stack_size;

    if (wanted < 1)
	wanted = 1;
    if (wanted > ERTS_MAX_NO_OF_SCHEDULERS) {
	wanted = ERTS_MAX_NO_OF_SCHEDULERS;
	res = ENOTSUP;
    }

    erts_block_system(0);

    while (actual < wanted) {
	ErtsSchedulerData *esdp = ERTS_SCHEDULER_IX(actual);
	actual++;
	ASSERT(actual == esdp->no);
#ifdef ERTS_ENABLE_LOCK_COUNT
	res = erts_lcnt_thr_create(&esdp->tid,sched_thread_func,(void*)esdp,&opts);
#else
	res = ethr_thr_create(&esdp->tid,sched_thread_func,(void*)esdp,&opts);
#endif
	if (res != 0) {
	    actual--;
	    break;
	}
    }
    
    erts_no_schedulers = actual;
    erts_release_system();

    if (actual < 1)
	erl_exit(1,
		 "Failed to create any scheduler-threads: %s (%d)\n",
		 erl_errno_id(res),
		 res);
    if (res != 0) {
	erts_dsprintf_buf_t *dsbufp = erts_create_logger_dsbuf();
	ASSERT(actual != wanted_no_schedulers);
	erts_dsprintf(dsbufp,
		      "Failed to create %bpu scheduler-threads (%s:%d); "
		      "only %bpu scheduler-thread%s created.\n",
		      wanted_no_schedulers, erl_errno_id(res), res,
		      actual, actual == 1 ? " was" : "s were");
	erts_send_error_to_logger_nogl(dsbufp);
    }
}

#endif /* ERTS_SMP */

static int
int_cmp(const void *vx, const void *vy)
{
    return *((int *) vx) - *((int *) vy);
}

static int
cpu_spread_order_cmp(const void *vx, const void *vy)
{
    erts_cpu_topology_t *x = (erts_cpu_topology_t *) vx;
    erts_cpu_topology_t *y = (erts_cpu_topology_t *) vy;

    if (x->thread != y->thread)
	return x->thread - y->thread;
    if (x->core != y->core)
	return x->core - y->core;
    if (x->processor_node != y->processor_node)
	return x->processor_node - y->processor_node;
    if (x->processor != y->processor)
	return x->processor - y->processor;
    if (x->node != y->node)
	return x->node - y->node;
    return 0;
}

static int
cpu_processor_spread_order_cmp(const void *vx, const void *vy)
{
    erts_cpu_topology_t *x = (erts_cpu_topology_t *) vx;
    erts_cpu_topology_t *y = (erts_cpu_topology_t *) vy;

    if (x->thread != y->thread)
	return x->thread - y->thread;
    if (x->processor_node != y->processor_node)
	return x->processor_node - y->processor_node;
    if (x->core != y->core)
	return x->core - y->core;
    if (x->node != y->node)
	return x->node - y->node;
    if (x->processor != y->processor)
	return x->processor - y->processor;
    return 0;
}

static int
cpu_thread_spread_order_cmp(const void *vx, const void *vy)
{
    erts_cpu_topology_t *x = (erts_cpu_topology_t *) vx;
    erts_cpu_topology_t *y = (erts_cpu_topology_t *) vy;

    if (x->thread != y->thread)
	return x->thread - y->thread;
    if (x->node != y->node)
	return x->node - y->node;
    if (x->processor != y->processor)
	return x->processor - y->processor;
    if (x->processor_node != y->processor_node)
	return x->processor_node - y->processor_node;
    if (x->core != y->core)
	return x->core - y->core;
    return 0;
}

static int
cpu_thread_no_node_processor_spread_order_cmp(const void *vx, const void *vy)
{
    erts_cpu_topology_t *x = (erts_cpu_topology_t *) vx;
    erts_cpu_topology_t *y = (erts_cpu_topology_t *) vy;

    if (x->thread != y->thread)
	return x->thread - y->thread;
    if (x->node != y->node)
	return x->node - y->node;
    if (x->core != y->core)
	return x->core - y->core;
    if (x->processor != y->processor)
	return x->processor - y->processor;
    return 0;
}

static int
cpu_no_node_processor_spread_order_cmp(const void *vx, const void *vy)
{
    erts_cpu_topology_t *x = (erts_cpu_topology_t *) vx;
    erts_cpu_topology_t *y = (erts_cpu_topology_t *) vy;

    if (x->node != y->node)
	return x->node - y->node;
    if (x->thread != y->thread)
	return x->thread - y->thread;
    if (x->core != y->core)
	return x->core - y->core;
    if (x->processor != y->processor)
	return x->processor - y->processor;
    return 0;
}

static int
cpu_no_node_thread_spread_order_cmp(const void *vx, const void *vy)
{
    erts_cpu_topology_t *x = (erts_cpu_topology_t *) vx;
    erts_cpu_topology_t *y = (erts_cpu_topology_t *) vy;

    if (x->node != y->node)
	return x->node - y->node;
    if (x->thread != y->thread)
	return x->thread - y->thread;
    if (x->processor != y->processor)
	return x->processor - y->processor;
    if (x->core != y->core)
	return x->core - y->core;
    return 0;
}

static int
cpu_no_spread_order_cmp(const void *vx, const void *vy)
{
    erts_cpu_topology_t *x = (erts_cpu_topology_t *) vx;
    erts_cpu_topology_t *y = (erts_cpu_topology_t *) vy;

    if (x->node != y->node)
	return x->node - y->node;
    if (x->processor != y->processor)
	return x->processor - y->processor;
    if (x->processor_node != y->processor_node)
	return x->processor_node - y->processor_node;
    if (x->core != y->core)
	return x->core - y->core;
    if (x->thread != y->thread)
	return x->thread - y->thread;
    return 0;
}

static ERTS_INLINE void
make_cpudata_id_seq(erts_cpu_topology_t *cpudata, int size, int no_node)
{
    int ix;
    int node = -1;
    int processor = -1;
    int processor_node = -1;
    int processor_node_node = -1;
    int core = -1;
    int thread = -1;
    int old_node = -1;
    int old_processor = -1;
    int old_processor_node = -1;
    int old_core = -1;
    int old_thread = -1;

    for (ix = 0; ix < size; ix++) {
	if (!no_node || cpudata[ix].node >= 0) {
	    if (old_node == cpudata[ix].node)
		cpudata[ix].node = node;
	    else {
		old_node = cpudata[ix].node;
		old_processor = processor = -1;
		if (!no_node)
		    old_processor_node = processor_node = -1;
		old_core = core = -1;
		old_thread = thread = -1;
		if (no_node || cpudata[ix].node >= 0)
		    cpudata[ix].node = ++node;
	    }
	}
	if (old_processor == cpudata[ix].processor)
	    cpudata[ix].processor = processor;
	else {
	    old_processor = cpudata[ix].processor;
	    if (!no_node)
		processor_node_node = old_processor_node = processor_node = -1;
	    old_core = core = -1;
	    old_thread = thread = -1;
	    cpudata[ix].processor = ++processor;
	}
	if (no_node && cpudata[ix].processor_node < 0)
	    old_processor_node = -1;
	else {
	    if (old_processor_node == cpudata[ix].processor_node) {
		if (no_node)
		    cpudata[ix].node = cpudata[ix].processor_node = node;
		else {
		    if (processor_node_node >= 0)
			cpudata[ix].node = processor_node_node;
		    cpudata[ix].processor_node = processor_node;
		}
	    }
	    else {
		old_processor_node = cpudata[ix].processor_node;
		old_core = core = -1;
		old_thread = thread = -1;
		if (no_node)
		    cpudata[ix].node = cpudata[ix].processor_node = ++node;
		else {
		    cpudata[ix].node = processor_node_node = ++node;
		    cpudata[ix].processor_node = ++processor_node;
		}
	    }
	}
	if (!no_node && cpudata[ix].processor_node < 0)
	    cpudata[ix].processor_node = 0;
	if (old_core == cpudata[ix].core)
	    cpudata[ix].core = core;
	else {
	    old_core = cpudata[ix].core;
	    old_thread = thread = -1;
	    cpudata[ix].core = ++core;
	}
	if (old_thread == cpudata[ix].thread)
	    cpudata[ix].thread = thread;
	else
	    old_thread = cpudata[ix].thread = ++thread;
    }
}

static void
cpu_bind_order_sort(erts_cpu_topology_t *cpudata,
		    int size,
		    ErtsCpuBindOrder bind_order,
		    int mk_seq)
{
    if (size > 1) {
	int no_node = 0;
	int (*cmp_func)(const void *, const void *);
	switch (bind_order) {
	case ERTS_CPU_BIND_SPREAD:
	    cmp_func = cpu_spread_order_cmp;
	    break;
	case ERTS_CPU_BIND_PROCESSOR_SPREAD:
	    cmp_func = cpu_processor_spread_order_cmp;
	    break;
	case ERTS_CPU_BIND_THREAD_SPREAD:
	    cmp_func = cpu_thread_spread_order_cmp;
	    break;
	case ERTS_CPU_BIND_THREAD_NO_NODE_PROCESSOR_SPREAD:
	    no_node = 1;
	    cmp_func = cpu_thread_no_node_processor_spread_order_cmp;
	    break;
	case ERTS_CPU_BIND_NO_NODE_PROCESSOR_SPREAD:
	    no_node = 1;
	    cmp_func = cpu_no_node_processor_spread_order_cmp;
	    break;
	case ERTS_CPU_BIND_NO_NODE_THREAD_SPREAD:
	    no_node = 1;
	    cmp_func = cpu_no_node_thread_spread_order_cmp;
	    break;
	case ERTS_CPU_BIND_NO_SPREAD:
	    cmp_func = cpu_no_spread_order_cmp;
	    break;
	default:
	    cmp_func = NULL;
	    erl_exit(ERTS_ABORT_EXIT,
		     "Bad cpu bind type: %d\n",
		     (int) cpu_bind_order);
	    break;
	}

	if (mk_seq)
	    make_cpudata_id_seq(cpudata, size, no_node);

	qsort(cpudata, size, sizeof(erts_cpu_topology_t), cmp_func);
    }
}

static int
processor_order_cmp(const void *vx, const void *vy)
{
    erts_cpu_topology_t *x = (erts_cpu_topology_t *) vx;
    erts_cpu_topology_t *y = (erts_cpu_topology_t *) vy;

    if (x->processor != y->processor)
	return x->processor - y->processor;
    if (x->node != y->node)
	return x->node - y->node;
    if (x->processor_node != y->processor_node)
	return x->processor_node - y->processor_node;
    if (x->core != y->core)
	return x->core - y->core;
    if (x->thread != y->thread)
	return x->thread - y->thread;
    return 0;
}

static void
check_cpu_bind(ErtsSchedulerData *esdp)
{
    int res;
    int cpu_id;
    erts_smp_runq_unlock(esdp->run_queue);
    erts_smp_rwmtx_rwlock(&erts_cpu_bind_rwmtx);
    cpu_id = scheduler2cpu_map[esdp->no].bind_id;
    if (cpu_id >= 0 && cpu_id != scheduler2cpu_map[esdp->no].bound_id) {
	res = erts_bind_to_cpu(erts_cpuinfo, cpu_id);
	if (res == 0)
	    esdp->cpu_id = scheduler2cpu_map[esdp->no].bound_id = cpu_id;
	else {
	    erts_dsprintf_buf_t *dsbufp = erts_create_logger_dsbuf();
	    erts_dsprintf(dsbufp, "Scheduler %d failed to bind to cpu %d: %s\n",
			  (int) esdp->no, cpu_id, erl_errno_id(-res));
	    erts_send_error_to_logger_nogl(dsbufp);
	    if (scheduler2cpu_map[esdp->no].bound_id >= 0)
		goto unbind;
	}
    }
    else if (cpu_id < 0 && scheduler2cpu_map[esdp->no].bound_id >= 0) {
    unbind:
	/* Get rid of old binding */
	res = erts_unbind_from_cpu(erts_cpuinfo);
	if (res == 0)
	    esdp->cpu_id = scheduler2cpu_map[esdp->no].bound_id = -1;
	else {
	    erts_dsprintf_buf_t *dsbufp = erts_create_logger_dsbuf();
	    erts_dsprintf(dsbufp, "Scheduler %d failed to unbind from cpu %d: %s\n",
			  (int) esdp->no, cpu_id, erl_errno_id(-res));
	    erts_send_error_to_logger_nogl(dsbufp);
	}
    }
    erts_smp_runq_lock(esdp->run_queue);
#ifdef ERTS_SMP
    if (erts_common_run_queue)
	erts_smp_atomic_set(&esdp->chk_cpu_bind, 0);
    else {
	esdp->run_queue->flags &= ~ERTS_RUNQ_FLG_CHK_CPU_BIND;
    }
#endif
    erts_smp_rwmtx_rwunlock(&erts_cpu_bind_rwmtx);

}

static void
signal_schedulers_bind_change(erts_cpu_topology_t *cpudata, int size)
{
    int s_ix = 1;
    int cpu_ix;

    if (cpu_bind_order != ERTS_CPU_BIND_NONE) {

	cpu_bind_order_sort(cpudata, size, cpu_bind_order, 1);

	for (cpu_ix = 0; cpu_ix < size && cpu_ix < erts_no_schedulers; cpu_ix++)
	    if (erts_is_cpu_available(erts_cpuinfo, cpudata[cpu_ix].logical))
		scheduler2cpu_map[s_ix++].bind_id = cpudata[cpu_ix].logical;
    }

    if (s_ix <= erts_no_schedulers)
	for (; s_ix <= erts_no_schedulers; s_ix++)
	    scheduler2cpu_map[s_ix].bind_id = -1;

#ifdef ERTS_SMP
    if (erts_common_run_queue) {
	for (s_ix = 0; s_ix < erts_no_schedulers; s_ix++)
	    erts_smp_atomic_set(&ERTS_SCHEDULER_IX(s_ix)->chk_cpu_bind, 1);
	wake_all_schedulers();
    }
    else {
	ERTS_FOREACH_RUNQ(rq,
	{
	    rq->flags |= ERTS_RUNQ_FLG_CHK_CPU_BIND;
	    wake_scheduler(rq, 0);
	});
    }
#else
    check_cpu_bind(erts_get_scheduler_data());
#endif
}

int
erts_init_scheduler_bind_type(char *how)
{
    if (erts_bind_to_cpu(erts_cpuinfo, -1) == -ENOTSUP)
	return ERTS_INIT_SCHED_BIND_TYPE_NOT_SUPPORTED;

    if (!system_cpudata && !user_cpudata)
	return ERTS_INIT_SCHED_BIND_TYPE_ERROR_NO_CPU_TOPOLOGY;

    if (sys_strcmp(how, "s") == 0)
	cpu_bind_order = ERTS_CPU_BIND_SPREAD;
    else if (sys_strcmp(how, "ps") == 0)
	cpu_bind_order = ERTS_CPU_BIND_PROCESSOR_SPREAD;
    else if (sys_strcmp(how, "ts") == 0)
	cpu_bind_order = ERTS_CPU_BIND_THREAD_SPREAD;
    else if (sys_strcmp(how, "db") == 0
	     || sys_strcmp(how, "tnnps") == 0)
	cpu_bind_order = ERTS_CPU_BIND_THREAD_NO_NODE_PROCESSOR_SPREAD;
    else if (sys_strcmp(how, "nnps") == 0)
	cpu_bind_order = ERTS_CPU_BIND_NO_NODE_PROCESSOR_SPREAD;
    else if (sys_strcmp(how, "nnts") == 0)
	cpu_bind_order = ERTS_CPU_BIND_NO_NODE_THREAD_SPREAD;
    else if (sys_strcmp(how, "ns") == 0)
	cpu_bind_order = ERTS_CPU_BIND_NO_SPREAD;
    else if (sys_strcmp(how, "u") == 0)
	cpu_bind_order = ERTS_CPU_BIND_NONE;
    else
	return ERTS_INIT_SCHED_BIND_TYPE_ERROR_NO_BAD_TYPE;

    return ERTS_INIT_SCHED_BIND_TYPE_SUCCESS;
}

typedef struct {
    int *id;
    int used;
    int size;
} ErtsCpuTopIdSeq;

typedef struct {
    ErtsCpuTopIdSeq logical;
    ErtsCpuTopIdSeq thread;
    ErtsCpuTopIdSeq core;
    ErtsCpuTopIdSeq processor_node;
    ErtsCpuTopIdSeq processor;
    ErtsCpuTopIdSeq node;
} ErtsCpuTopEntry;

static void
init_cpu_top_entry(ErtsCpuTopEntry *cte)
{
    int size = 10;
    cte->logical.id = erts_alloc(ERTS_ALC_T_TMP_CPU_IDS,
				 sizeof(int)*size);
    cte->logical.size = size;
    cte->thread.id = erts_alloc(ERTS_ALC_T_TMP_CPU_IDS,
				sizeof(int)*size);
    cte->thread.size = size;
    cte->core.id = erts_alloc(ERTS_ALC_T_TMP_CPU_IDS,
			      sizeof(int)*size);
    cte->core.size = size;
    cte->processor_node.id = erts_alloc(ERTS_ALC_T_TMP_CPU_IDS,
					sizeof(int)*size);
    cte->processor_node.size = size;
    cte->processor.id = erts_alloc(ERTS_ALC_T_TMP_CPU_IDS,
				   sizeof(int)*size);
    cte->processor.size = size;
    cte->node.id = erts_alloc(ERTS_ALC_T_TMP_CPU_IDS,
			      sizeof(int)*size);
    cte->node.size = size;
}

static void
destroy_cpu_top_entry(ErtsCpuTopEntry *cte)
{
    erts_free(ERTS_ALC_T_TMP_CPU_IDS, cte->logical.id);
    erts_free(ERTS_ALC_T_TMP_CPU_IDS, cte->thread.id);
    erts_free(ERTS_ALC_T_TMP_CPU_IDS, cte->core.id);
    erts_free(ERTS_ALC_T_TMP_CPU_IDS, cte->processor_node.id);
    erts_free(ERTS_ALC_T_TMP_CPU_IDS, cte->processor.id);
    erts_free(ERTS_ALC_T_TMP_CPU_IDS, cte->node.id);
}

static int
get_cput_value_or_range(int *v, int *vr, char **str)
{
    long l;
    char *c = *str;
    errno = 0;
    if (!isdigit((unsigned char)*c))
	return ERTS_INIT_CPU_TOPOLOGY_INVALID_ID;
    l = strtol(c, &c, 10);
    if (errno != 0 || l < 0 || ERTS_MAX_CPU_TOPOLOGY_ID < l)
	return ERTS_INIT_CPU_TOPOLOGY_INVALID_ID;
    *v = (int) l;
    if (*c == '-') {
	c++;
	if (!isdigit((unsigned char)*c))
	    return ERTS_INIT_CPU_TOPOLOGY_INVALID_ID_RANGE;
	l = strtol(c, &c, 10);
	if (errno != 0 || l < 0 || ERTS_MAX_CPU_TOPOLOGY_ID < l)
	    return ERTS_INIT_CPU_TOPOLOGY_INVALID_ID_RANGE;
	*vr = (int) l;
    }
    *str = c;
    return ERTS_INIT_CPU_TOPOLOGY_OK;
}

static int
get_cput_id_seq(ErtsCpuTopIdSeq *idseq, char **str)
{
    int ix = 0;
    int need_size = 0;
    char *c = *str;

    while (1) {
	int res;
	int val;
	int nids;
	int val_range = -1;
	res = get_cput_value_or_range(&val, &val_range, &c);
	if (res != ERTS_INIT_CPU_TOPOLOGY_OK)
	    return res;
	if (val_range < 0 || val_range == val)
	    nids = 1;
	else {
	    if (val_range > val)
		nids = val_range - val + 1;
	    else
		nids = val - val_range + 1;
	}
	need_size += nids;
	if (need_size > idseq->size) {
	    idseq->size = need_size + 10;
	    idseq->id = erts_realloc(ERTS_ALC_T_TMP_CPU_IDS,
				      idseq->id,
				      sizeof(int)*idseq->size);
	}
	if (nids == 1)
	    idseq->id[ix++] = val;
	else if (val_range > val) {
	    for (; val <= val_range; val++)
		idseq->id[ix++] = val;
	}
	else {
	    for (; val >= val_range; val--)
		idseq->id[ix++] = val;
	}
	if (*c != ',')
	    break;
	c++;
    }
    *str = c;
    idseq->used = ix;
    return ERTS_INIT_CPU_TOPOLOGY_OK;
}

static int
get_cput_entry(ErtsCpuTopEntry *cput, char **str)
{
    int h;
    char *c = *str;

    cput->logical.used = 0;
    cput->thread.id[0] = 0;
    cput->thread.used = 1;
    cput->core.id[0] = 0;
    cput->core.used = 1;
    cput->processor_node.id[0] = -1;
    cput->processor_node.used = 1;
    cput->processor.id[0] = 0;
    cput->processor.used = 1;
    cput->node.id[0] = -1;
    cput->node.used = 1;

    h = ERTS_TOPOLOGY_MAX_DEPTH;
    while (*c != ':' && *c != '\0') {
	int res;
	ErtsCpuTopIdSeq *idseqp;
	switch (*c++) {
	case 'L':
	    if (h <= ERTS_TOPOLOGY_LOGICAL)
		return ERTS_INIT_CPU_TOPOLOGY_INVALID_HIERARCHY;
	    idseqp = &cput->logical;
	    h = ERTS_TOPOLOGY_LOGICAL;
	    break;
	case 't':
	case 'T':
	    if (h <= ERTS_TOPOLOGY_THREAD)
		return ERTS_INIT_CPU_TOPOLOGY_INVALID_HIERARCHY;
	    idseqp = &cput->thread;
	    h = ERTS_TOPOLOGY_THREAD;
	    break;
	case 'c':
	case 'C':
	    if (h <= ERTS_TOPOLOGY_CORE)
		return ERTS_INIT_CPU_TOPOLOGY_INVALID_HIERARCHY;
	    idseqp = &cput->core;
	    h = ERTS_TOPOLOGY_CORE;
	    break;
	case 'p':
	case 'P':
	    if (h <= ERTS_TOPOLOGY_PROCESSOR)
		return ERTS_INIT_CPU_TOPOLOGY_INVALID_HIERARCHY;
	    idseqp = &cput->processor;
	    h = ERTS_TOPOLOGY_PROCESSOR;
	    break;
	case 'n':
	case 'N':
	    if (h <= ERTS_TOPOLOGY_PROCESSOR) {
	    do_node:
		if (h <= ERTS_TOPOLOGY_NODE)
		    return ERTS_INIT_CPU_TOPOLOGY_INVALID_HIERARCHY;
		idseqp = &cput->node;
		h = ERTS_TOPOLOGY_NODE;
	    }
	    else {
		int p_node = 0;
		char *p_chk = c;
		while (*p_chk != '\0' && *p_chk != ':') {
		    if (*p_chk == 'p' || *p_chk == 'P') {
			p_node = 1;
			break;
		    }
		    p_chk++;
		}
		if (!p_node)
		    goto do_node;
		if (h <= ERTS_TOPOLOGY_PROCESSOR_NODE)
		    return ERTS_INIT_CPU_TOPOLOGY_INVALID_HIERARCHY;
		idseqp = &cput->processor_node;
		h = ERTS_TOPOLOGY_PROCESSOR_NODE;
	    }
	    break;
	default:
	    return ERTS_INIT_CPU_TOPOLOGY_INVALID_ID_TYPE;
	}
	res = get_cput_id_seq(idseqp, &c);
	if (res != ERTS_INIT_CPU_TOPOLOGY_OK)
		return res;
    }

    if (cput->logical.used < 1)
	return ERTS_INIT_CPU_TOPOLOGY_MISSING_LID;

    if (*c == ':') {
	c++;
    }

    if (cput->thread.used != 1
	&& cput->thread.used != cput->logical.used)
	return ERTS_INIT_CPU_TOPOLOGY_INVALID_ID_RANGE;
    if (cput->core.used != 1
	&& cput->core.used != cput->logical.used)
	return ERTS_INIT_CPU_TOPOLOGY_INVALID_ID_RANGE;
    if (cput->processor_node.used != 1
	&& cput->processor_node.used != cput->logical.used)
	return ERTS_INIT_CPU_TOPOLOGY_INVALID_ID_RANGE;
    if (cput->processor.used != 1
	&& cput->processor.used != cput->logical.used)
	return ERTS_INIT_CPU_TOPOLOGY_INVALID_ID_RANGE;
    if (cput->node.used != 1
	&& cput->node.used != cput->logical.used)
	return ERTS_INIT_CPU_TOPOLOGY_INVALID_ID_RANGE;

    *str = c;
    return ERTS_INIT_CPU_TOPOLOGY_OK;
}

static int
verify_topology(erts_cpu_topology_t *cpudata, int size)
{
    if (size > 0) {
	int *logical;
	int node, processor, no_nodes, i;

	/* Verify logical ids */
	logical = erts_alloc(ERTS_ALC_T_TMP, sizeof(int)*size);

	for (i = 0; i < user_cpudata_size; i++)
	    logical[i] = user_cpudata[i].logical;

	qsort(logical, user_cpudata_size, sizeof(int), int_cmp);
	for (i = 0; i < user_cpudata_size-1; i++) {
	    if (logical[i] == logical[i+1]) {
		erts_free(ERTS_ALC_T_TMP, logical);
		return ERTS_INIT_CPU_TOPOLOGY_NOT_UNIQUE_LIDS;
	    }
	}

	erts_free(ERTS_ALC_T_TMP, logical);

	qsort(cpudata, size, sizeof(erts_cpu_topology_t), processor_order_cmp);

	/* Verify unique entities */

	for (i = 1; i < user_cpudata_size; i++) {
	    if (user_cpudata[i-1].processor == user_cpudata[i].processor
		&& user_cpudata[i-1].node == user_cpudata[i].node
		&& (user_cpudata[i-1].processor_node
		    == user_cpudata[i].processor_node)
		&& user_cpudata[i-1].core == user_cpudata[i].core
		&& user_cpudata[i-1].thread == user_cpudata[i].thread) {
		return ERTS_INIT_CPU_TOPOLOGY_NOT_UNIQUE_ENTITIES;
	    }
	}

	/* Verify numa nodes */
	node = cpudata[0].node;
	processor = cpudata[0].processor;
	no_nodes = cpudata[0].node < 0 && cpudata[0].processor_node < 0;
	for (i = 1; i < size; i++) {
	    if (no_nodes) {
		if (cpudata[i].node >= 0 || cpudata[i].processor_node >= 0)
		    return ERTS_INIT_CPU_TOPOLOGY_INVALID_NODES;
	    }
	    else {
		if (cpudata[i].processor == processor && cpudata[i].node != node)
		    return ERTS_INIT_CPU_TOPOLOGY_INVALID_NODES;
		node = cpudata[i].node;
		processor = cpudata[i].processor;
		if (node >= 0 && cpudata[i].processor_node >= 0)
		    return ERTS_INIT_CPU_TOPOLOGY_INVALID_NODES;
		if (node < 0 && cpudata[i].processor_node < 0)
		    return ERTS_INIT_CPU_TOPOLOGY_INVALID_NODES;
	    }
	}
    }

    return ERTS_INIT_CPU_TOPOLOGY_OK;
}

int
erts_init_cpu_topology(char *topology_str)
{
    ErtsCpuTopEntry cput;
    int need_size;
    char *c;
    int ix;
    int error = ERTS_INIT_CPU_TOPOLOGY_OK;

    if (user_cpudata)
	erts_free(ERTS_ALC_T_CPUDATA, user_cpudata);
    user_cpudata_size = 10;

    user_cpudata = erts_alloc(ERTS_ALC_T_CPUDATA,
			      (sizeof(erts_cpu_topology_t)
			       * user_cpudata_size));

    init_cpu_top_entry(&cput);

    ix = 0;
    need_size = 0;

    c = topology_str;
    if (*c == '\0') {
	error = ERTS_INIT_CPU_TOPOLOGY_MISSING;
	goto fail;
    }
    do {
	int r;
	error = get_cput_entry(&cput, &c);
	if (error != ERTS_INIT_CPU_TOPOLOGY_OK)
	    goto fail;
	need_size += cput.logical.used;
	if (user_cpudata_size < need_size) {
	    user_cpudata_size = need_size + 10;
	    user_cpudata = erts_realloc(ERTS_ALC_T_CPUDATA,
					user_cpudata,
					(sizeof(erts_cpu_topology_t)
					 * user_cpudata_size));
	}

	ASSERT(cput.thread.used == 1
	       || cput.thread.used == cput.logical.used);
	ASSERT(cput.core.used == 1
	       || cput.core.used == cput.logical.used);
	ASSERT(cput.processor_node.used == 1
	       || cput.processor_node.used == cput.logical.used);
	ASSERT(cput.processor.used == 1
	       || cput.processor.used == cput.logical.used);
	ASSERT(cput.node.used == 1
	       || cput.node.used == cput.logical.used);

	for (r = 0; r < cput.logical.used; r++) {
	    user_cpudata[ix].logical = cput.logical.id[r];
	    user_cpudata[ix].thread =
		cput.thread.id[cput.thread.used == 1 ? 0 : r];
	    user_cpudata[ix].core =
		cput.core.id[cput.core.used == 1 ? 0 : r];
	    user_cpudata[ix].processor_node =
		cput.processor_node.id[cput.processor_node.used == 1 ? 0 : r];
	    user_cpudata[ix].processor =
		cput.processor.id[cput.processor.used == 1 ? 0 : r];
	    user_cpudata[ix].node =
		cput.node.id[cput.node.used == 1 ? 0 : r];
	    ix++;
	}
    } while (*c != '\0');

    if (user_cpudata_size != ix) {
	user_cpudata_size = ix;
	user_cpudata = erts_realloc(ERTS_ALC_T_CPUDATA,
				    user_cpudata,
				    (sizeof(erts_cpu_topology_t)
				     * user_cpudata_size));
    }

    error = verify_topology(user_cpudata, user_cpudata_size);
    if (error == ERTS_INIT_CPU_TOPOLOGY_OK) {
	destroy_cpu_top_entry(&cput);
	return ERTS_INIT_CPU_TOPOLOGY_OK;
    }

 fail:
    if (user_cpudata)
	erts_free(ERTS_ALC_T_CPUDATA, user_cpudata);
    user_cpudata_size = 0;
    destroy_cpu_top_entry(&cput);
    return error;
}

#define ERTS_GET_CPU_TOPOLOGY_ERROR		-1
#define ERTS_GET_USED_CPU_TOPOLOGY		0
#define ERTS_GET_DETECTED_CPU_TOPOLOGY		1
#define ERTS_GET_DEFINED_CPU_TOPOLOGY		2

static Eterm get_cpu_topology_term(Process *c_p, int type);

Eterm
erts_set_cpu_topology(Process *c_p, Eterm term)
{
    erts_cpu_topology_t *cpudata = NULL;
    int cpudata_size = 0;
    Eterm res;

    erts_smp_rwmtx_rwlock(&erts_cpu_bind_rwmtx);
    res = get_cpu_topology_term(c_p, ERTS_GET_USED_CPU_TOPOLOGY);
    if (term == am_undefined) {
	if (user_cpudata)
	    erts_free(ERTS_ALC_T_CPUDATA, user_cpudata);
	user_cpudata = NULL;
	user_cpudata_size = 0;

	if (cpu_bind_order != ERTS_CPU_BIND_NONE && system_cpudata) {
	    cpudata_size = system_cpudata_size;
	    cpudata = erts_alloc(ERTS_ALC_T_TMP,
				 (sizeof(erts_cpu_topology_t)
				  * cpudata_size));

	    sys_memcpy((void *) cpudata,
		       (void *) system_cpudata,
		       sizeof(erts_cpu_topology_t)*cpudata_size);
	}
    }
    else if (is_not_list(term)) {
    error:
	res = THE_NON_VALUE;
	goto done;
    }
    else {
	Eterm list = term;
	int ix = 0;

	cpudata_size = 100;
	cpudata = erts_alloc(ERTS_ALC_T_TMP,
			     (sizeof(erts_cpu_topology_t)
			      * cpudata_size));

	while (is_list(list)) {
	    Eterm *lp = list_val(list);
	    Eterm cpu = CAR(lp);
	    Eterm* tp;
	    Sint id;
		
	    if (is_not_tuple(cpu))
		goto error;

	    tp = tuple_val(cpu);

	    if (arityval(tp[0]) != 7 || tp[1] != am_cpu)
		goto error;

	    if (ix >= cpudata_size) {
		cpudata_size += 100;
		cpudata = erts_realloc(ERTS_ALC_T_TMP,
				       cpudata,
				       (sizeof(erts_cpu_topology_t)
					* cpudata_size));
	    }

	    id = signed_val(tp[2]);
	    if (id < -1 || ERTS_MAX_CPU_TOPOLOGY_ID < id)
		goto error;
	    cpudata[ix].node = (int) id;

	    id = signed_val(tp[3]);
	    if (id < -1 || ERTS_MAX_CPU_TOPOLOGY_ID < id)
		goto error;
	    cpudata[ix].processor = (int) id;

	    id = signed_val(tp[4]);
	    if (id < -1 || ERTS_MAX_CPU_TOPOLOGY_ID < id)
		goto error;
	    cpudata[ix].processor_node = (int) id;

	    id = signed_val(tp[5]);
	    if (id < -1 || ERTS_MAX_CPU_TOPOLOGY_ID < id)
		goto error;
	    cpudata[ix].core = (int) id;

	    id = signed_val(tp[6]);
	    if (id < -1 || ERTS_MAX_CPU_TOPOLOGY_ID < id)
		goto error;
	    cpudata[ix].thread = (int) id;

	    id = signed_val(tp[7]);
	    if (id < -1 || ERTS_MAX_CPU_TOPOLOGY_ID < id)
		goto error;
	    cpudata[ix].logical = (int) id;

	    list = CDR(lp);
	    ix++;
	}

	if (is_not_nil(list))
	    goto error;
	
	cpudata_size = ix;

	if (ERTS_INIT_CPU_TOPOLOGY_OK != verify_topology(cpudata, cpudata_size))
	    goto error;

	if (user_cpudata_size != cpudata_size) {
	    if (user_cpudata)
		erts_free(ERTS_ALC_T_CPUDATA, user_cpudata);
	    user_cpudata = erts_alloc(ERTS_ALC_T_CPUDATA,
				      sizeof(erts_cpu_topology_t)*cpudata_size);
	    user_cpudata_size = cpudata_size;
	}

	sys_memcpy((void *) user_cpudata,
		   (void *) cpudata,
		   sizeof(erts_cpu_topology_t)*cpudata_size);
    }

    signal_schedulers_bind_change(cpudata, cpudata_size);

 done:
    erts_smp_rwmtx_rwunlock(&erts_cpu_bind_rwmtx);

    if (cpudata)
	erts_free(ERTS_ALC_T_TMP, cpudata);

    return res;
}

static Eterm
bound_schedulers_term(ErtsCpuBindOrder order)
{
    switch (order) {
    case ERTS_CPU_BIND_SPREAD: {
	ERTS_DECL_AM(spread);
	return AM_spread;
    }
    case ERTS_CPU_BIND_PROCESSOR_SPREAD: {
	ERTS_DECL_AM(processor_spread);
	return AM_processor_spread;
    }
    case ERTS_CPU_BIND_THREAD_SPREAD: {
	ERTS_DECL_AM(thread_spread);
	return AM_thread_spread;
    }
    case ERTS_CPU_BIND_THREAD_NO_NODE_PROCESSOR_SPREAD: {
	ERTS_DECL_AM(thread_no_node_processor_spread);
	return AM_thread_no_node_processor_spread;
    }
    case ERTS_CPU_BIND_NO_NODE_PROCESSOR_SPREAD: {
	ERTS_DECL_AM(no_node_processor_spread);
	return AM_no_node_processor_spread;
    }
    case ERTS_CPU_BIND_NO_NODE_THREAD_SPREAD: {
	ERTS_DECL_AM(no_node_thread_spread);
	return AM_no_node_thread_spread;
    }
    case ERTS_CPU_BIND_NO_SPREAD: {
	ERTS_DECL_AM(no_spread);
	return AM_no_spread;
    }
    case ERTS_CPU_BIND_NONE: {
	ERTS_DECL_AM(unbound);
	return AM_unbound;
    }
    default:
	ASSERT(0);
	return THE_NON_VALUE;
    }
}

Eterm
erts_bound_schedulers_term(Process *c_p)
{
    ErtsCpuBindOrder order;
    erts_smp_rwmtx_rlock(&erts_cpu_bind_rwmtx);
    order = cpu_bind_order;
    erts_smp_rwmtx_runlock(&erts_cpu_bind_rwmtx);
    return bound_schedulers_term(order);
}

static void
create_tmp_cpu_topology_copy(erts_cpu_topology_t **cpudata, int *cpudata_size)
{
    if (user_cpudata) {
	*cpudata_size = user_cpudata_size;
	*cpudata = erts_alloc(ERTS_ALC_T_TMP,
			      (sizeof(erts_cpu_topology_t)
			       * (*cpudata_size)));
	sys_memcpy((void *) *cpudata,
		   (void *) user_cpudata,
		   sizeof(erts_cpu_topology_t)*(*cpudata_size));
    }
    else if (system_cpudata) {
	*cpudata_size = system_cpudata_size;
	*cpudata = erts_alloc(ERTS_ALC_T_TMP,
			      (sizeof(erts_cpu_topology_t)
			       * (*cpudata_size)));
	sys_memcpy((void *) *cpudata,
		   (void *) system_cpudata,
		   sizeof(erts_cpu_topology_t)*(*cpudata_size));
    }
    else {
	*cpudata = NULL;
	*cpudata_size = 0;
    }
}

static void
destroy_tmp_cpu_topology_copy(erts_cpu_topology_t *cpudata)
{
    if (cpudata)
	erts_free(ERTS_ALC_T_TMP, cpudata);
}

Eterm
erts_bind_schedulers(Process *c_p, Eterm how)
{
    Eterm res;
    erts_cpu_topology_t *cpudata;
    int cpudata_size;
    ErtsCpuBindOrder old_cpu_bind_order;

    erts_smp_rwmtx_rwlock(&erts_cpu_bind_rwmtx);

    if (erts_bind_to_cpu(erts_cpuinfo, -1) == -ENOTSUP) {
	ERTS_BIF_PREP_ERROR(res, c_p, EXC_NOTSUP);
    }
    else {

	old_cpu_bind_order = cpu_bind_order;

	if (ERTS_IS_ATOM_STR("spread", how))
	    cpu_bind_order = ERTS_CPU_BIND_SPREAD;
	else if (ERTS_IS_ATOM_STR("processor_spread", how))
	    cpu_bind_order = ERTS_CPU_BIND_PROCESSOR_SPREAD;
	else if (ERTS_IS_ATOM_STR("thread_spread", how))
	    cpu_bind_order = ERTS_CPU_BIND_THREAD_SPREAD;
	else if (ERTS_IS_ATOM_STR("default_bind", how)
		 || ERTS_IS_ATOM_STR("thread_no_node_processor_spread", how))
	    cpu_bind_order = ERTS_CPU_BIND_THREAD_NO_NODE_PROCESSOR_SPREAD;
	else if (ERTS_IS_ATOM_STR("no_node_processor_spread", how))
	    cpu_bind_order = ERTS_CPU_BIND_NO_NODE_PROCESSOR_SPREAD;
	else if (ERTS_IS_ATOM_STR("no_node_thread_spread", how))
	    cpu_bind_order = ERTS_CPU_BIND_NO_NODE_THREAD_SPREAD;
	else if (ERTS_IS_ATOM_STR("no_spread", how))
	    cpu_bind_order = ERTS_CPU_BIND_NO_SPREAD;
	else if (ERTS_IS_ATOM_STR("unbound", how))
	    cpu_bind_order = ERTS_CPU_BIND_NONE;
	else {
	    cpu_bind_order = old_cpu_bind_order;
	    ERTS_BIF_PREP_ERROR(res, c_p, BADARG);
	    goto done;
	}

	create_tmp_cpu_topology_copy(&cpudata, &cpudata_size);

	if (!cpudata) {
	    cpu_bind_order = old_cpu_bind_order;
	    ERTS_BIF_PREP_ERROR(res, c_p, BADARG);
	    goto done;
	}

	signal_schedulers_bind_change(cpudata, cpudata_size);

	destroy_tmp_cpu_topology_copy(cpudata);
    
	res = bound_schedulers_term(old_cpu_bind_order);
    }

 done:

    erts_smp_rwmtx_rwunlock(&erts_cpu_bind_rwmtx);

    return res;
}

Eterm
erts_fake_scheduler_bindings(Process *p, Eterm how)
{
    ErtsCpuBindOrder fake_cpu_bind_order;
    erts_cpu_topology_t *cpudata;
    int cpudata_size;
    Eterm res;

    if (ERTS_IS_ATOM_STR("spread", how))
	fake_cpu_bind_order = ERTS_CPU_BIND_SPREAD;
    else if (ERTS_IS_ATOM_STR("processor_spread", how))
	fake_cpu_bind_order = ERTS_CPU_BIND_PROCESSOR_SPREAD;
    else if (ERTS_IS_ATOM_STR("thread_spread", how))
	fake_cpu_bind_order = ERTS_CPU_BIND_THREAD_SPREAD;
    else if (ERTS_IS_ATOM_STR("default_bind", how)
	     || ERTS_IS_ATOM_STR("thread_no_node_processor_spread", how))
	fake_cpu_bind_order = ERTS_CPU_BIND_THREAD_NO_NODE_PROCESSOR_SPREAD;
    else if (ERTS_IS_ATOM_STR("no_node_processor_spread", how))
	fake_cpu_bind_order = ERTS_CPU_BIND_NO_NODE_PROCESSOR_SPREAD;
    else if (ERTS_IS_ATOM_STR("no_node_thread_spread", how))
	fake_cpu_bind_order = ERTS_CPU_BIND_NO_NODE_THREAD_SPREAD;
    else if (ERTS_IS_ATOM_STR("no_spread", how))
	fake_cpu_bind_order = ERTS_CPU_BIND_NO_SPREAD;
    else if (ERTS_IS_ATOM_STR("unbound", how))
	fake_cpu_bind_order = ERTS_CPU_BIND_NONE;
    else {
	ERTS_BIF_PREP_ERROR(res, p, BADARG);
	return res;
    }

    erts_smp_rwmtx_rlock(&erts_cpu_bind_rwmtx);
    create_tmp_cpu_topology_copy(&cpudata, &cpudata_size);
    erts_smp_rwmtx_runlock(&erts_cpu_bind_rwmtx);

    if (!cpudata || fake_cpu_bind_order == ERTS_CPU_BIND_NONE)
	ERTS_BIF_PREP_RET(res, am_false);
    else {
	int i;
	Eterm *hp;
	
	cpu_bind_order_sort(cpudata, cpudata_size, fake_cpu_bind_order, 1);

#ifdef ERTS_FAKE_SCHED_BIND_PRINT_SORTED_CPU_DATA

	erts_fprintf(stderr, "node:          ");
	for (i = 0; i < cpudata_size; i++)
	    erts_fprintf(stderr, " %2d", cpudata[i].node);
	erts_fprintf(stderr, "\n");
	erts_fprintf(stderr, "processor:     ");
	for (i = 0; i < cpudata_size; i++)
	    erts_fprintf(stderr, " %2d", cpudata[i].processor);
	erts_fprintf(stderr, "\n");
	if (fake_cpu_bind_order != ERTS_CPU_BIND_THREAD_NO_NODE_PROCESSOR_SPREAD
	    && fake_cpu_bind_order != ERTS_CPU_BIND_NO_NODE_PROCESSOR_SPREAD
	    && fake_cpu_bind_order != ERTS_CPU_BIND_NO_NODE_THREAD_SPREAD) {
	    erts_fprintf(stderr, "processor_node:");
	    for (i = 0; i < cpudata_size; i++)
		erts_fprintf(stderr, " %2d", cpudata[i].processor_node);
	    erts_fprintf(stderr, "\n");
	}
	erts_fprintf(stderr, "core:          ");
	for (i = 0; i < cpudata_size; i++)
	    erts_fprintf(stderr, " %2d", cpudata[i].core);
	erts_fprintf(stderr, "\n");
	erts_fprintf(stderr, "thread:        ");
	for (i = 0; i < cpudata_size; i++)
	    erts_fprintf(stderr, " %2d", cpudata[i].thread);
	erts_fprintf(stderr, "\n");
	erts_fprintf(stderr, "logical:       ");
	for (i = 0; i < cpudata_size; i++)
	    erts_fprintf(stderr, " %2d", cpudata[i].logical);
	erts_fprintf(stderr, "\n");
#endif

	hp = HAlloc(p, cpudata_size+1);
	ERTS_BIF_PREP_RET(res, make_tuple(hp));
	*hp++ = make_arityval((Uint) cpudata_size);
	for (i = 0; i < cpudata_size; i++)
	    *hp++ = make_small((Uint) cpudata[i].logical);
    }

    destroy_tmp_cpu_topology_copy(cpudata);

    return res;
}

Eterm
erts_get_schedulers_binds(Process *c_p)
{
    int ix;
    ERTS_DECL_AM(unbound);
    Eterm *hp = HAlloc(c_p, erts_no_schedulers+1);
    Eterm res = make_tuple(hp);

    *(hp++) = make_arityval(erts_no_schedulers);
    erts_smp_rwmtx_rlock(&erts_cpu_bind_rwmtx);
    for (ix = 1; ix <= erts_no_schedulers; ix++)
	*(hp++) = (scheduler2cpu_map[ix].bound_id >= 0
		   ? make_small(scheduler2cpu_map[ix].bound_id)
		   : AM_unbound);
    erts_smp_rwmtx_runlock(&erts_cpu_bind_rwmtx);
    return res;
}

static Eterm
bld_topology_term(Eterm **hpp,
		  Uint *hszp,
		  erts_cpu_topology_t *cpudata,
		  int size)
{
    Eterm res = NIL;
    int i;

    if (size == 0)
	return am_undefined;

    for (i = size-1; i >= 0; i--) {
	res = erts_bld_cons(hpp,
			    hszp,
			    erts_bld_tuple(hpp,
					   hszp,
					   7,
					   am_cpu,
					   make_small(cpudata[i].node),
					   make_small(cpudata[i].processor),
					   make_small(cpudata[i].processor_node),
					   make_small(cpudata[i].core),
					   make_small(cpudata[i].thread),
					   make_small(cpudata[i].logical)),
			    res);
    }
    return res;
}

static Eterm
get_cpu_topology_term(Process *c_p, int type)
{
#ifdef DEBUG
    Eterm *hp_end;
#endif
    Eterm *hp;
    Uint hsz;
    Eterm res = THE_NON_VALUE;
    erts_cpu_topology_t *cpudata = NULL;
    int size = 0;

    switch (type) {
    case ERTS_GET_USED_CPU_TOPOLOGY:
	if (user_cpudata)
	    goto defined;
	else
	    goto detected;
    case ERTS_GET_DETECTED_CPU_TOPOLOGY:
    detected:
	if (!system_cpudata)
	    res = am_undefined;
	else {
	    size = system_cpudata_size;
	    cpudata = erts_alloc(ERTS_ALC_T_TMP,
				 (sizeof(erts_cpu_topology_t)
				  * size));
	    sys_memcpy((void *) cpudata,
		       (void *) system_cpudata,
		       sizeof(erts_cpu_topology_t)*size);
	}
	break;
    case ERTS_GET_DEFINED_CPU_TOPOLOGY:
    defined:
	if (!user_cpudata)
	    res = am_undefined;
	else {
	    size = user_cpudata_size;
	    cpudata = user_cpudata;
	}
	break;
    default:
	erl_exit(ERTS_ABORT_EXIT, "Bad cpu topology type: %d\n", type);
	break;
    }

    if (res == am_undefined) {
	ASSERT(!cpudata);
	return res;
    }

    hsz = 0;

    bld_topology_term(NULL, &hsz,
		      cpudata, size);

    hp = HAlloc(c_p, hsz);

#ifdef DEBUG
    hp_end = hp + hsz;
#endif

    res = bld_topology_term(&hp, NULL,
			    cpudata, size);

    ASSERT(hp_end == hp);

    if (cpudata && cpudata != system_cpudata && cpudata != user_cpudata)
	erts_free(ERTS_ALC_T_TMP, cpudata);

    return res;
}

Eterm
erts_get_cpu_topology_term(Process *c_p, Eterm which)
{
    Eterm res;
    int type;
    erts_smp_rwmtx_rlock(&erts_cpu_bind_rwmtx);
    if (ERTS_IS_ATOM_STR("used", which))
	type = ERTS_GET_USED_CPU_TOPOLOGY;
    else if (ERTS_IS_ATOM_STR("detected", which))
	type = ERTS_GET_DETECTED_CPU_TOPOLOGY;
    else if (ERTS_IS_ATOM_STR("defined", which))
	type = ERTS_GET_DEFINED_CPU_TOPOLOGY;
    else
	type = ERTS_GET_CPU_TOPOLOGY_ERROR;
    if (type == ERTS_GET_CPU_TOPOLOGY_ERROR)
	res = THE_NON_VALUE;
    else
	res = get_cpu_topology_term(c_p, type);
    erts_smp_rwmtx_runlock(&erts_cpu_bind_rwmtx);
    return res;
}

static void
early_cpu_bind_init(void)
{
    user_cpudata = NULL;
    user_cpudata_size = 0;

    system_cpudata_size = erts_get_cpu_topology_size(erts_cpuinfo);
    system_cpudata = erts_alloc(ERTS_ALC_T_CPUDATA,
				(sizeof(erts_cpu_topology_t)
				 * system_cpudata_size));

    cpu_bind_order = ERTS_CPU_BIND_NONE;

    if (!erts_get_cpu_topology(erts_cpuinfo, system_cpudata)
	|| ERTS_INIT_CPU_TOPOLOGY_OK != verify_topology(system_cpudata,
							system_cpudata_size)) {
	erts_free(ERTS_ALC_T_CPUDATA, system_cpudata);
	system_cpudata = NULL;
	system_cpudata_size = 0;
    }
}

static void
late_cpu_bind_init(void)
{
    int ix;

    erts_smp_rwmtx_init(&erts_cpu_bind_rwmtx, "cpu_bind");

    scheduler2cpu_map = erts_alloc(ERTS_ALC_T_CPUDATA,
				   (sizeof(ErtsCpuBindData)
				    * (erts_no_schedulers+1)));
    for (ix = 1; ix <= erts_no_schedulers; ix++) {
	scheduler2cpu_map[ix].bind_id = -1;
	scheduler2cpu_map[ix].bound_id = -1;
    }

    if (cpu_bind_order != ERTS_CPU_BIND_NONE) {
	erts_cpu_topology_t *cpudata;
	int cpudata_size;
	create_tmp_cpu_topology_copy(&cpudata, &cpudata_size);
	ASSERT(cpudata);
	signal_schedulers_bind_change(cpudata, cpudata_size);
	destroy_tmp_cpu_topology_copy(cpudata);
    }
}

#ifdef ERTS_SMP

static void
add_pend_suspend(Process *suspendee,
		 Eterm originator_pid,
		 void (*handle_func)(Process *,
				     ErtsProcLocks,
				     int,
				     Eterm))
{
    ErtsPendingSuspend *psp = erts_alloc(ERTS_ALC_T_PEND_SUSPEND,
					 sizeof(ErtsPendingSuspend));
    psp->next = NULL;
#ifdef DEBUG
#ifdef ARCH_64
    psp->end = (ErtsPendingSuspend *) 0xdeaddeaddeaddead;
#else
    psp->end = (ErtsPendingSuspend *) 0xdeaddead;
#endif
#endif
    psp->pid = originator_pid;
    psp->handle_func = handle_func;

    if (suspendee->pending_suspenders)
	suspendee->pending_suspenders->end->next = psp;
    else
	suspendee->pending_suspenders = psp;
    suspendee->pending_suspenders->end = psp;
}

static void
handle_pending_suspend(Process *p, ErtsProcLocks p_locks)
{
    ErtsPendingSuspend *psp;
    int is_alive = !ERTS_PROC_IS_EXITING(p);

    ERTS_SMP_LC_ASSERT(p_locks & ERTS_PROC_LOCK_STATUS);

    /*
     * New pending suspenders might appear while we are processing
     * (since we may release the status lock on p while processing).
     */
    while (p->pending_suspenders) {
	psp = p->pending_suspenders;
	p->pending_suspenders = NULL;
	while (psp) {
	    ErtsPendingSuspend *free_psp;
	    (*psp->handle_func)(p, p_locks, is_alive, psp->pid);
	    free_psp = psp;
	    psp = psp->next;
	    erts_free(ERTS_ALC_T_PEND_SUSPEND, (void *) free_psp);
	}
    }
    
}

static ERTS_INLINE void
cancel_suspend_of_suspendee(Process *p, ErtsProcLocks p_locks)
{
    if (is_not_nil(p->suspendee)) {
	Process *rp;
	if (!(p_locks & ERTS_PROC_LOCK_STATUS))
	    erts_smp_proc_lock(p, ERTS_PROC_LOCK_STATUS);
	rp = erts_pid2proc(p, p_locks|ERTS_PROC_LOCK_STATUS,
			   p->suspendee, ERTS_PROC_LOCK_STATUS);
	if (rp) {
	    erts_resume(rp, ERTS_PROC_LOCK_STATUS);
	    erts_smp_proc_unlock(rp, ERTS_PROC_LOCK_STATUS);
	}
	if (!(p_locks & ERTS_PROC_LOCK_STATUS))
	    erts_smp_proc_unlock(p, ERTS_PROC_LOCK_STATUS);
	p->suspendee = NIL;
    }
}

static void
handle_pend_sync_suspend(Process *suspendee,
			 ErtsProcLocks suspendee_locks,
			 int suspendee_alive,
			 Eterm suspender_pid)
{
    Process *suspender;

    ERTS_SMP_LC_ASSERT(suspendee_locks & ERTS_PROC_LOCK_STATUS);

    suspender = erts_pid2proc(suspendee,
			      suspendee_locks,
			      suspender_pid,
			      ERTS_PROC_LOCK_STATUS);
    if (suspender) {
	ASSERT(is_nil(suspender->suspendee));
	if (suspendee_alive) {
	    ErtsRunQueue *rq = erts_get_runq_proc(suspendee);
	    erts_smp_runq_lock(rq);
	    suspend_process(rq, suspendee);
	    erts_smp_runq_unlock(rq);
	    suspender->suspendee = suspendee->id;
	}
	/* suspender is suspended waiting for suspendee to suspend;
	   resume suspender */
	resume_process(suspender);
	erts_smp_proc_unlock(suspender, ERTS_PROC_LOCK_STATUS);
    }
}

/*
 * Like erts_pid2proc() but:
 *
 * * At least ERTS_PROC_LOCK_MAIN have to be held on c_p.
 * * At least ERTS_PROC_LOCK_MAIN have to be taken on pid.
 * * It also waits for proc to be in a state != running and garbing.
 * * If ERTS_PROC_LOCK_BUSY is returned, the calling process has to
 *   yield (ERTS_BIF_YIELD[0-3]()). c_p might in this case have been
 *   suspended.
 */


Process *
erts_pid2proc_not_running(Process *c_p, ErtsProcLocks c_p_locks,
			  Eterm pid, ErtsProcLocks pid_locks)
{
    Process *rp;
    int unlock_c_p_status;

    ERTS_SMP_LC_ASSERT(c_p_locks == erts_proc_lc_my_proc_locks(c_p));

    ERTS_SMP_LC_ASSERT(c_p_locks & ERTS_PROC_LOCK_MAIN);
    ERTS_SMP_LC_ASSERT(pid_locks & (ERTS_PROC_LOCK_MAIN|ERTS_PROC_LOCK_STATUS));

    if (c_p->id == pid)
	return erts_pid2proc(c_p, c_p_locks, pid, pid_locks);

    if (c_p_locks & ERTS_PROC_LOCK_STATUS)
	unlock_c_p_status = 0;
    else {
	unlock_c_p_status = 1;
	erts_smp_proc_lock(c_p, ERTS_PROC_LOCK_STATUS);
    }

    if (c_p->suspendee == pid) {
	/* Process previously suspended by c_p (below)... */
	ErtsProcLocks rp_locks = pid_locks|ERTS_PROC_LOCK_STATUS;
	rp = erts_pid2proc(c_p, c_p_locks|ERTS_PROC_LOCK_STATUS, pid, rp_locks);
	c_p->suspendee = NIL;
	ASSERT(c_p->flags & F_P2PNR_RESCHED);
	c_p->flags &= ~F_P2PNR_RESCHED;
	if (rp)
	    resume_process(rp);
    }
    else {
	ErtsRunQueue *cp_rq, *rp_rq;

	rp = erts_pid2proc(c_p, c_p_locks|ERTS_PROC_LOCK_STATUS,
			   pid, ERTS_PROC_LOCK_STATUS);

	if (!rp) {
	    c_p->flags &= ~F_P2PNR_RESCHED;
	    goto done;
	}

	ASSERT(!(c_p->flags & F_P2PNR_RESCHED));

	cp_rq = erts_get_runq_proc(c_p);
	rp_rq = erts_get_runq_proc(rp);
	erts_smp_runqs_lock(cp_rq, rp_rq);
	if (rp->runq_flags & ERTS_PROC_RUNQ_FLG_RUNNING) {
	running:
	    /* Phiu... */

	    /*
	     * If we got pending suspenders and suspend ourselves waiting
	     * to suspend another process we might deadlock.
	     * In this case we have to yield, be suspended by
	     * someone else and then do it all over again.
	     */
	    if (!c_p->pending_suspenders) {
		/* Mark rp pending for suspend by c_p */
		add_pend_suspend(rp, c_p->id, handle_pend_sync_suspend);
		ASSERT(is_nil(c_p->suspendee));

		/* Suspend c_p; when rp is suspended c_p will be resumed. */
		suspend_process(cp_rq, c_p);
		c_p->flags |= F_P2PNR_RESCHED;
	    }
	    /* Yield (caller is assumed to yield immediately in bif). */
	    erts_smp_proc_unlock(rp, ERTS_PROC_LOCK_STATUS);
	    rp = ERTS_PROC_LOCK_BUSY;
	}
	else {
	    ErtsProcLocks need_locks = pid_locks & ~ERTS_PROC_LOCK_STATUS;
	    if (need_locks && erts_smp_proc_trylock(rp, need_locks) == EBUSY) {
		erts_smp_runqs_unlock(cp_rq, rp_rq);
		erts_smp_proc_unlock(rp, ERTS_PROC_LOCK_STATUS);
		rp = erts_pid2proc(c_p, c_p_locks|ERTS_PROC_LOCK_STATUS,
				   pid, pid_locks|ERTS_PROC_LOCK_STATUS);
		if (!rp)
		    goto done;
		/* run-queues may have changed */
		cp_rq = erts_get_runq_proc(c_p);
		rp_rq = erts_get_runq_proc(rp);
		erts_smp_runqs_lock(cp_rq, rp_rq);
		if (rp->runq_flags & ERTS_PROC_RUNQ_FLG_RUNNING) {
		    /* Ahh... */
		    erts_smp_proc_unlock(rp,
					 pid_locks & ~ERTS_PROC_LOCK_STATUS);
		    goto running;
		}
	    }

	    /* rp is not running and we got the locks we want... */
	}
	erts_smp_runqs_unlock(cp_rq, rp_rq);
    }

 done:
    if (rp && rp != ERTS_PROC_LOCK_BUSY && !(pid_locks & ERTS_PROC_LOCK_STATUS))
	erts_smp_proc_unlock(rp, ERTS_PROC_LOCK_STATUS);
    if (unlock_c_p_status)
	erts_smp_proc_unlock(c_p, ERTS_PROC_LOCK_STATUS);
    return rp;
}

/*
 * erts_pid2proc_nropt() is normally the same as
 * erts_pid2proc_not_running(). However it is only
 * to be used when 'not running' is a pure optimization,
 * not a requirement.
 */

Process *
erts_pid2proc_nropt(Process *c_p, ErtsProcLocks c_p_locks,
		    Eterm pid, ErtsProcLocks pid_locks)
{
    if (erts_disable_proc_not_running_opt)
	return erts_pid2proc(c_p, c_p_locks, pid, pid_locks);
    else
	return erts_pid2proc_not_running(c_p, c_p_locks, pid, pid_locks);
}

static ERTS_INLINE void
do_bif_suspend_process(ErtsSuspendMonitor *smon,
		       Process *suspendee,
		       ErtsRunQueue *locked_runq)
{
    ASSERT(suspendee);
    ASSERT(!suspendee->is_exiting);
    ERTS_SMP_LC_ASSERT(ERTS_PROC_LOCK_STATUS
		       & erts_proc_lc_my_proc_locks(suspendee));
    if (smon) {
	if (!smon->active) {
	    ErtsRunQueue *rq;

	    if (locked_runq)
		rq = locked_runq;
	    else {
		rq = erts_get_runq_proc(suspendee);
		erts_smp_runq_lock(rq);
	    }

	    suspend_process(rq, suspendee);

	    if (!locked_runq)
		erts_smp_runq_unlock(rq);
	}
	smon->active += smon->pending;
	ASSERT(smon->active);
	smon->pending = 0;
    }
    
}

static void
handle_pend_bif_sync_suspend(Process *suspendee,
			     ErtsProcLocks suspendee_locks,
			     int suspendee_alive,
			     Eterm suspender_pid)
{
    Process *suspender;

    ERTS_SMP_LC_ASSERT(suspendee_locks & ERTS_PROC_LOCK_STATUS);

    suspender = erts_pid2proc(suspendee,
			      suspendee_locks,
			      suspender_pid,
			      ERTS_PROC_LOCK_LINK|ERTS_PROC_LOCK_STATUS);
    if (suspender) {
	ASSERT(is_nil(suspender->suspendee));
	if (!suspendee_alive)
	    erts_delete_suspend_monitor(&suspender->suspend_monitors,
					suspendee->id);
	else {
	    ErtsSuspendMonitor *smon;
	    smon = erts_lookup_suspend_monitor(suspender->suspend_monitors,
					       suspendee->id);
	    do_bif_suspend_process(smon, suspendee, NULL);
	    suspender->suspendee = suspendee->id;
	}
	/* suspender is suspended waiting for suspendee to suspend;
	   resume suspender */
	resume_process(suspender);
	erts_smp_proc_unlock(suspender,
			     ERTS_PROC_LOCK_LINK|ERTS_PROC_LOCK_STATUS);
    }
}

static void
handle_pend_bif_async_suspend(Process *suspendee,
			      ErtsProcLocks suspendee_locks,
			      int suspendee_alive,
			      Eterm suspender_pid)
{

    Process *suspender;

    ERTS_SMP_LC_ASSERT(suspendee_locks & ERTS_PROC_LOCK_STATUS);

    suspender = erts_pid2proc(suspendee,
			      suspendee_locks,
			      suspender_pid,
			      ERTS_PROC_LOCK_LINK);
    if (suspender) {
	ASSERT(is_nil(suspender->suspendee));
	if (!suspendee_alive)
	    erts_delete_suspend_monitor(&suspender->suspend_monitors,
					suspendee->id);
	else {
	    ErtsSuspendMonitor *smon;
	    smon = erts_lookup_suspend_monitor(suspender->suspend_monitors,
					       suspendee->id);
	    do_bif_suspend_process(smon, suspendee, NULL);
	}
	erts_smp_proc_unlock(suspender, ERTS_PROC_LOCK_LINK);
    }
}

#endif /* ERTS_SMP */

/*
 * The erlang:suspend_process/2 BIF
 */

BIF_RETTYPE
suspend_process_2(BIF_ALIST_2)
{
    Eterm res;
    Process* suspendee = NULL;
    ErtsSuspendMonitor *smon;
    ErtsProcLocks xlocks = (ErtsProcLocks) 0;

    /* Options and default values: */
    int asynchronous = 0;
    int unless_suspending = 0;


    if (BIF_P->id == BIF_ARG_1)
	goto badarg; /* We are not allowed to suspend ourselves */

    if (is_not_nil(BIF_ARG_2)) {
	/* Parse option list */
	Eterm arg = BIF_ARG_2;

	while (is_list(arg)) {
	    Eterm *lp = list_val(arg);
	    arg = CAR(lp);
	    switch (arg) {
	    case am_unless_suspending:
		unless_suspending = 1;
		break;
	    case am_asynchronous:
		asynchronous = 1;
		break;
	    default:
		goto badarg;
	    }
	    arg = CDR(lp);
	}
	if (is_not_nil(arg))
	    goto badarg;
    }

    xlocks = ERTS_PROC_LOCK_LINK | (asynchronous
				    ? (ErtsProcLocks) 0
				    : ERTS_PROC_LOCK_STATUS);

    erts_smp_proc_lock(BIF_P, xlocks);

    suspendee = erts_pid2proc(BIF_P,
			      ERTS_PROC_LOCK_MAIN|xlocks,
			      BIF_ARG_1,
			      ERTS_PROC_LOCK_STATUS);
    if (!suspendee)
	goto no_suspendee;

    smon = erts_add_or_lookup_suspend_monitor(&BIF_P->suspend_monitors,
					      BIF_ARG_1);
#ifndef ERTS_SMP /* no ERTS_SMP */

    /* This is really a piece of cake without SMP support... */
    if (!smon->active) {
	suspend_process(erts_common_run_queue, suspendee);
	smon->active++;
	res = am_true;
    }
    else if (unless_suspending)
	res = am_false;
    else if (smon->active == INT_MAX)
	goto system_limit;
    else {
	smon->active++;
	res = am_true;
    }

#else /* ERTS_SMP */

    /* ... but a little trickier with SMP support ... */

    if (asynchronous) {
	/* --- Asynchronous suspend begin ---------------------------------- */

	ERTS_SMP_LC_ASSERT(ERTS_PROC_LOCK_LINK
			   & erts_proc_lc_my_proc_locks(BIF_P));
	ERTS_SMP_LC_ASSERT(ERTS_PROC_LOCK_STATUS
			   == erts_proc_lc_my_proc_locks(suspendee));

	if (smon->active) {
	    smon->active += smon->pending;
	    smon->pending = 0;
	    if (unless_suspending)
		res = am_false;
	    else if (smon->active == INT_MAX)
		goto system_limit;
	    else {
		smon->active++;
		res = am_true;
	    }
	    /* done */
	}
	else {
	    /* We havn't got any active suspends on the suspendee */
	    if (smon->pending && unless_suspending)
		res = am_false;
	    else {
		ErtsRunQueue *rq;
		if (smon->pending == INT_MAX)
		    goto system_limit;

		smon->pending++;
		rq = erts_get_runq_proc(suspendee);
		erts_smp_runq_lock(rq);

		if (suspendee->runq_flags & ERTS_PROC_RUNQ_FLG_RUNNING)
		    add_pend_suspend(suspendee,
				     BIF_P->id,
				     handle_pend_bif_async_suspend);
		else
		    do_bif_suspend_process(smon, suspendee, rq);
		erts_smp_runq_unlock(rq);

		res = am_true;
	    }
	    /* done */
	}
	/* --- Asynchronous suspend end ------------------------------------ */
    }
    else /* if (!asynchronous) */ {
	/* --- Synchronous suspend begin ----------------------------------- */

	ERTS_SMP_LC_ASSERT(((ERTS_PROC_LOCK_LINK|ERTS_PROC_LOCK_STATUS)
			    & erts_proc_lc_my_proc_locks(BIF_P))
			   == (ERTS_PROC_LOCK_LINK|ERTS_PROC_LOCK_STATUS));
	ERTS_SMP_LC_ASSERT(ERTS_PROC_LOCK_STATUS
			   == erts_proc_lc_my_proc_locks(suspendee));

	if (BIF_P->suspendee == BIF_ARG_1) {
	    /* We are back after a yield and the suspendee
	       has been suspended on behalf of us. */
	    ASSERT(smon->active >= 1);
	    BIF_P->suspendee = NIL;
	    res = (!unless_suspending || smon->active == 1
		   ? am_true
		   : am_false);
	    /* done */
	}
	else if (smon->active) {
	    if (unless_suspending)
		res = am_false;
	    else {
		smon->active++;
		res = am_true;
	    }
	    /* done */
	}
	else {
	    ErtsRunQueue *cp_rq, *s_rq;
	    /* We haven't got any active suspends on the suspendee */

	    /*
	     * If we have pending suspenders and suspend ourselves waiting
	     * to suspend another process, or suspend another process
	     * we might deadlock. In this case we have to yield,
	     * be suspended by someone else, and then do it all over again.
	     */
	    if (BIF_P->pending_suspenders)
		goto yield;

	    if (!unless_suspending && smon->pending == INT_MAX)
		goto system_limit;
	    if (!unless_suspending || smon->pending == 0)
		smon->pending++;

	    cp_rq = erts_get_runq_proc(BIF_P);
	    s_rq = erts_get_runq_proc(suspendee);
	    erts_smp_runqs_lock(cp_rq, s_rq);
	    if (!(suspendee->runq_flags & ERTS_PROC_RUNQ_FLG_RUNNING)) {
		do_bif_suspend_process(smon, suspendee, s_rq);
		erts_smp_runqs_unlock(cp_rq, s_rq);
		res = (!unless_suspending || smon->active == 1
		       ? am_true
		       : am_false);
		/* done */
	    }
	    else {
		/* Mark suspendee pending for suspend by BIF_P */
		add_pend_suspend(suspendee,
				 BIF_P->id,
				 handle_pend_bif_sync_suspend);

		ASSERT(is_nil(BIF_P->suspendee));

		/*
		 * Suspend BIF_P; when suspendee is suspended, BIF_P
		 * will be resumed and this BIF will be called again.
		 * This time with BIF_P->suspendee == BIF_ARG_1 (see
		 * above).
		 */
		suspend_process(cp_rq, BIF_P);
		erts_smp_runqs_unlock(cp_rq, s_rq);
		goto yield;
	    }
	}
	/* --- Synchronous suspend end ------------------------------------- */
    }

#endif /* ERTS_SMP */

    ASSERT(suspendee->status == P_SUSPENDED || (asynchronous && smon->pending));
    ASSERT(suspendee->status == P_SUSPENDED || !smon->active);

    erts_smp_proc_unlock(suspendee, ERTS_PROC_LOCK_STATUS);
    erts_smp_proc_unlock(BIF_P, xlocks);
    BIF_RET(res);

 system_limit:
    ERTS_BIF_PREP_ERROR(res, BIF_P, SYSTEM_LIMIT);
    goto do_return;

 no_suspendee:
#ifdef ERTS_SMP
    BIF_P->suspendee = NIL;
#endif
    erts_delete_suspend_monitor(&BIF_P->suspend_monitors, BIF_ARG_1);

 badarg:
    ERTS_BIF_PREP_ERROR(res, BIF_P, BADARG);
#ifdef ERTS_SMP
    goto do_return;

 yield:
    ERTS_BIF_PREP_YIELD2(res, bif_export[BIF_suspend_process_2],
			 BIF_P, BIF_ARG_1, BIF_ARG_2);
#endif

 do_return:
    if (suspendee)
	erts_smp_proc_unlock(suspendee, ERTS_PROC_LOCK_STATUS);
    if (xlocks)
	erts_smp_proc_unlock(BIF_P, xlocks);
    return res;

}


/*
 * The erlang:resume_process/1 BIF
 */

BIF_RETTYPE
resume_process_1(BIF_ALIST_1)
{
    ErtsSuspendMonitor *smon;
    Process *suspendee;
    int is_active;
 
    if (BIF_P->id == BIF_ARG_1)
	BIF_ERROR(BIF_P, BADARG);

    erts_smp_proc_lock(BIF_P, ERTS_PROC_LOCK_LINK);
    smon = erts_lookup_suspend_monitor(BIF_P->suspend_monitors, BIF_ARG_1);

    if (!smon) {
	/* No previous suspend or dead suspendee */
	goto error;
    }
    else if (smon->pending) {
	smon->pending--;
	ASSERT(smon->pending >= 0);
	if (smon->active) {
	    smon->active += smon->pending;
	    smon->pending = 0;
	}
	is_active = smon->active;
    }
    else if (smon->active) {
	smon->active--;
	ASSERT(smon->pending >= 0);
	is_active = 1;
    }
    else {
	/* No previous suspend or dead suspendee */
	goto error;
    }

    if (smon->active || smon->pending || !is_active) {
	/* Leave the suspendee as it is; just verify that it is still alive */
	suspendee = erts_pid2proc(BIF_P,
				  ERTS_PROC_LOCK_MAIN|ERTS_PROC_LOCK_LINK,
				  BIF_ARG_1,
				  0);
	if (!suspendee)
	    goto no_suspendee;

    }
    else {
	/* Resume */
	suspendee = erts_pid2proc(BIF_P,
				  ERTS_PROC_LOCK_MAIN|ERTS_PROC_LOCK_LINK,
				  BIF_ARG_1,
				  ERTS_PROC_LOCK_STATUS);
	if (!suspendee)
	    goto no_suspendee;

	ASSERT(suspendee->status == P_SUSPENDED
	       || (suspendee->status == P_GARBING
		   && suspendee->gcstatus == P_SUSPENDED));
	resume_process(suspendee);

	erts_smp_proc_unlock(suspendee, ERTS_PROC_LOCK_STATUS);
    }

    if (!smon->active && !smon->pending)
	erts_delete_suspend_monitor(&BIF_P->suspend_monitors, BIF_ARG_1);

    erts_smp_proc_unlock(BIF_P, ERTS_PROC_LOCK_LINK);

    BIF_RET(am_true);

 no_suspendee:
    /* cleanup */
    erts_delete_suspend_monitor(&BIF_P->suspend_monitors, BIF_ARG_1);

 error:
    erts_smp_proc_unlock(BIF_P, ERTS_PROC_LOCK_LINK);
    BIF_ERROR(BIF_P, BADARG);
}

Uint
erts_run_queues_len(Uint *qlen)
{
    int i = 0;
    Uint len = 0;
    ERTS_ATOMIC_FOREACH_RUNQ(rq,
    {
	if (qlen)
	    qlen[i++] = rq->procs.len;
	len += rq->procs.len;
    }
	);
    return len;
}

#ifdef HARDDEBUG_RUNQS
static void
check_procs_runq(ErtsRunQueue *runq, Process *p_in_q, Process *p_not_in_q)
{
    int len[ERTS_NO_PROC_PRIO_LEVELS] = {0};
    int tot_len;
    int prioq, prio;
    int found_p_in_q;
    Process *p, *prevp;

    found_p_in_q = 0;
    for (prioq = 0; prioq < ERTS_NO_PROC_PRIO_LEVELS - 1; prioq++) {
	prevp = NULL;
	for (p = runq->procs.prio[prioq].first; p; p = p->next) {
	    ASSERT(p != p_not_in_q);
	    if (p == p_in_q)
		found_p_in_q = 1;
	    switch (p->prio) {
	    case PRIORITY_MAX:
	    case PRIORITY_HIGH:
	    case PRIORITY_NORMAL:
		ASSERT(prioq == p->prio);
		break;
	    case PRIORITY_LOW:
		ASSERT(prioq == PRIORITY_NORMAL);
		break;
	    default:
		ASSERT(!"Bad prio on process");
	    }
	    len[p->prio]++;
	    ASSERT(prevp == p->prev);
	    if (p->prev) {
		ASSERT(p->prev->next == p);
	    }
	    else {
		ASSERT(runq->procs.prio[prioq].first == p);
	    }
	    if (p->next) {
		ASSERT(p->next->prev == p);
	    }
	    else {
		ASSERT(runq->procs.prio[prioq].last == p);
	    }
	    ASSERT(p->run_queue == runq);
	    prevp = p;
	}
    }

    ASSERT(!p_in_q || found_p_in_q);

    tot_len = 0;
    for (prio = 0; prio < ERTS_NO_PROC_PRIO_LEVELS; prio++) {
	ASSERT(len[prio] == runq->procs.prio_info[prio].len);
	if (len[prio]) {
	    ASSERT(runq->flags & (1 << prio));
	}
	else {
	    ASSERT(!(runq->flags & (1 << prio)));
	}
	tot_len += len[prio];
    }
    ASSERT(runq->procs.len == tot_len);    
}
#  define ERTS_DBG_CHK_PROCS_RUNQ(RQ) check_procs_runq((RQ), NULL, NULL)
#  define ERTS_DBG_CHK_PROCS_RUNQ_PROC(RQ, P) check_procs_runq((RQ), (P), NULL)
#  define ERTS_DBG_CHK_PROCS_RUNQ_NOPROC(RQ, P) check_procs_runq((RQ), NULL, (P))
#else
#  define ERTS_DBG_CHK_PROCS_RUNQ(RQ)
#  define ERTS_DBG_CHK_PROCS_RUNQ_PROC(RQ, P)
#  define ERTS_DBG_CHK_PROCS_RUNQ_NOPROC(RQ, P)
#endif


static ERTS_INLINE void
enqueue_process(ErtsRunQueue *runq, Process *p)
{
    ErtsRunPrioQueue *rpq;
    ErtsRunQueueInfo *rqi;

    ERTS_SMP_LC_ASSERT(erts_smp_lc_runq_is_locked(runq));
    ERTS_SMP_LC_ASSERT(ERTS_PROC_LOCK_STATUS & erts_proc_lc_my_proc_locks(p));

    ASSERT(p->bound_runq || !(runq->flags & ERTS_RUNQ_FLG_SUSPENDED));

    rqi = &runq->procs.prio_info[p->prio];
    rqi->len++;
    if (rqi->max_len < rqi->len)
	rqi->max_len = rqi->len;

    runq->procs.len++;
    runq->len++;
    if (runq->max_len < runq->len)
	runq->max_len = runq->len;

    runq->flags |= (1 << p->prio);

    rpq = (p->prio == PRIORITY_LOW
	   ? &runq->procs.prio[PRIORITY_NORMAL]
	   : &runq->procs.prio[p->prio]);

    p->next = NULL;
    p->prev = rpq->last;
    if (rpq->last)
	rpq->last->next = p;
    else
	rpq->first = p;
    rpq->last = p;

    switch (p->status) {
    case P_EXITING:
	break;
    case P_GARBING:
	p->gcstatus = P_RUNABLE;
	break;
    default:
	p->status = P_RUNABLE;
	break;
    }

#ifdef ERTS_SMP
    p->status_flags |= ERTS_PROC_SFLG_INRUNQ;
#endif

    ERTS_DBG_CHK_PROCS_RUNQ_PROC(runq, p);
}


static ERTS_INLINE int
dequeue_process(ErtsRunQueue *runq, Process *p)
{
    ErtsRunPrioQueue *rpq;
    int res = 1;

    ERTS_SMP_LC_ASSERT(erts_smp_lc_runq_is_locked(runq));
    ERTS_SMP_LC_ASSERT(ERTS_PROC_LOCK_STATUS & erts_proc_lc_my_proc_locks(p));

    ERTS_DBG_CHK_PROCS_RUNQ(runq);

    rpq = &runq->procs.prio[p->prio == PRIORITY_LOW ? PRIORITY_NORMAL : p->prio];
    if (p->prev) {
	p->prev->next = p->next;
    }
    else if (rpq->first == p) {
	rpq->first = p->next;
    }
    else {
	res = 0;
    }
    if (p->next) {
	p->next->prev = p->prev;
    }
    else if (rpq->last == p) {
	rpq->last = p->prev;
    }
    else {
	ASSERT(res == 0);
    }

    if (res) {

	if (--runq->procs.prio_info[p->prio].len == 0)
	    runq->flags &= ~(1 << p->prio);
	runq->procs.len--;
	runq->len--;

#ifdef ERTS_SMP
	p->status_flags &= ~ERTS_PROC_SFLG_INRUNQ;
#endif
    }

    ERTS_DBG_CHK_PROCS_RUNQ_NOPROC(runq, p);
    return res;
}

/* schedule a process */
static ERTS_INLINE void
internal_add_to_runq(ErtsRunQueue *runq, Process *p)
{
    Uint32 prev_status = p->status;
    ErtsRunQueue *add_runq;
#ifdef ERTS_SMP

    ERTS_SMP_LC_ASSERT(ERTS_PROC_LOCK_STATUS & erts_proc_lc_my_proc_locks(p));
    ERTS_SMP_LC_ASSERT(erts_smp_lc_runq_is_locked(runq));

    if (p->status_flags & ERTS_PROC_SFLG_INRUNQ)
	return;
    else if (p->runq_flags & ERTS_PROC_RUNQ_FLG_RUNNING) {
	ASSERT(p->status != P_SUSPENDED);
	ERTS_DBG_CHK_PROCS_RUNQ_NOPROC(runq, p);
	p->status_flags |= ERTS_PROC_SFLG_PENDADD2SCHEDQ;
	return;
    }
    ASSERT(!p->scheduler_data);
#endif

    ERTS_DBG_CHK_PROCS_RUNQ_NOPROC(runq, p);
#ifndef ERTS_SMP
    /* Never schedule a suspended process (ok in smp case) */
    ASSERT(p->status != P_SUSPENDED);
    add_runq = runq;

#else
    ASSERT(!p->bound_runq || p->bound_runq == p->run_queue);
    if (p->bound_runq) {
	if (p->bound_runq == runq)
	    add_runq = runq;
	else {
	    add_runq = p->bound_runq;
	    erts_smp_xrunq_lock(runq, add_runq);
	}
    }
    else {
	add_runq = erts_check_emigration_need(runq, p->prio);
	if (!add_runq)
	    add_runq = runq;
	else /* Process emigrated */
	    p->run_queue = add_runq;
    }
#endif

    /* Enqueue the process */
    enqueue_process(add_runq, p);

    if ((erts_system_profile_flags.runnable_procs)
	&& (prev_status == P_WAITING
	    || prev_status == P_SUSPENDED)) {
    	profile_runnable_proc(p, am_active);
    }

    smp_notify_inc_runq(add_runq);

    if (add_runq != runq)
	erts_smp_runq_unlock(add_runq);
}


void
erts_add_to_runq(Process *p)
{
    ErtsRunQueue *runq = erts_get_runq_proc(p);
    erts_smp_runq_lock(runq);
    internal_add_to_runq(runq, p);
    erts_smp_runq_unlock(runq);
}

/* Possibly remove a scheduled process we need to suspend */

static int
remove_proc_from_runq(ErtsRunQueue *rq, Process *p, int to_inactive)
{
    int res;

    ERTS_SMP_LC_ASSERT(ERTS_PROC_LOCK_STATUS & erts_proc_lc_my_proc_locks(p));

#ifdef ERTS_SMP
    if (p->status_flags & ERTS_PROC_SFLG_PENDADD2SCHEDQ) {
	p->status_flags &= ~ERTS_PROC_SFLG_PENDADD2SCHEDQ;
	ASSERT(!remove_proc_from_runq(rq, p, 0));
	return 1;
    }
#endif

    res = dequeue_process(rq, p);

    if (res && erts_system_profile_flags.runnable_procs && to_inactive)
	profile_runnable_proc(p, am_inactive);

#ifdef ERTS_SMP
    ASSERT(!(p->status_flags & ERTS_PROC_SFLG_INRUNQ));
#endif

    return res;
}

#ifdef ERTS_SMP

ErtsMigrateResult
erts_proc_migrate(Process *p, ErtsProcLocks *plcks,
		  ErtsRunQueue *from_rq, int *from_locked,
		  ErtsRunQueue *to_rq, int *to_locked)
{
    ERTS_SMP_LC_ASSERT(*plcks == erts_proc_lc_my_proc_locks(p));
    ERTS_SMP_LC_ASSERT((ERTS_PROC_LOCK_STATUS & *plcks)
		       || from_locked);
    ERTS_SMP_LC_CHK_RUNQ_LOCK(from_rq, *from_locked);
    ERTS_SMP_LC_CHK_RUNQ_LOCK(to_rq, *to_locked);

    ASSERT(!erts_common_run_queue);
    
    /*
     * If we have the lock on the run queue to migrate to,
     * check that it isn't suspended. If it is suspended,
     * we will refuse to migrate to it anyway.
     */
    if (*to_locked && (to_rq->flags & ERTS_RUNQ_FLG_SUSPENDED))
	return ERTS_MIGRATE_FAILED_RUNQ_SUSPENDED;

    /* We need status lock on process and locks on both run queues */

    if (!(ERTS_PROC_LOCK_STATUS & *plcks)) {
	if (erts_smp_proc_trylock(p, ERTS_PROC_LOCK_STATUS) == EBUSY) {
	    ErtsProcLocks lcks = *plcks;
	    Eterm pid = p->id;
	    Process *proc = *plcks ? p : NULL;

	    if (*from_locked) {
		*from_locked = 0;
		erts_smp_runq_unlock(from_rq);
	    }
	    if (*to_locked) {
		*to_locked = 0;
		erts_smp_runq_unlock(to_rq);
	    }

	    proc = erts_pid2proc_opt(proc,
				     lcks,
				     pid,
				     lcks|ERTS_PROC_LOCK_STATUS,
				     ERTS_P2P_FLG_ALLOW_OTHER_X);
	    if (!proc) {
		*plcks = 0;
		return ERTS_MIGRATE_FAILED_NOT_IN_RUNQ;
	    }
	    ASSERT(proc == p);
	}
	*plcks |= ERTS_PROC_LOCK_STATUS;
    }

    ASSERT(!p->bound_runq);

    ERTS_SMP_LC_CHK_RUNQ_LOCK(from_rq, *from_locked);
    ERTS_SMP_LC_CHK_RUNQ_LOCK(to_rq, *to_locked);

    if (p->run_queue != from_rq)
	return ERTS_MIGRATE_FAILED_RUNQ_CHANGED;

    if (!*from_locked || !*to_locked) {
	if (from_rq < to_rq) {
	    if (!*to_locked) {
		if (!*from_locked)
		    erts_smp_runq_lock(from_rq);
		erts_smp_runq_lock(to_rq);
	    }
	    else if (erts_smp_runq_trylock(from_rq) == EBUSY) {
		erts_smp_runq_unlock(to_rq);
		erts_smp_runq_lock(from_rq);
		erts_smp_runq_lock(to_rq);
	    }
	}
	else {
	    if (!*from_locked) {
		if (!*to_locked)
		    erts_smp_runq_lock(to_rq);
		erts_smp_runq_lock(from_rq);
	    }
	    else if (erts_smp_runq_trylock(to_rq) == EBUSY) {
		erts_smp_runq_unlock(from_rq);
		erts_smp_runq_lock(to_rq);
		erts_smp_runq_lock(from_rq);
	    }
	}
	*to_locked = *from_locked = 1;
    }

    ERTS_SMP_LC_CHK_RUNQ_LOCK(from_rq, *from_locked);
    ERTS_SMP_LC_CHK_RUNQ_LOCK(to_rq, *to_locked);

    /* Ok we now got all locks we need; do it... */

    /* Refuse to migrate to a suspended run queue */
    if (to_rq->flags & ERTS_RUNQ_FLG_SUSPENDED)
	return ERTS_MIGRATE_FAILED_RUNQ_SUSPENDED;

    if ((p->runq_flags & ERTS_PROC_RUNQ_FLG_RUNNING)
	|| !(p->status_flags & ERTS_PROC_SFLG_INRUNQ))
	return ERTS_MIGRATE_FAILED_NOT_IN_RUNQ;

    dequeue_process(from_rq, p);
    p->run_queue = to_rq;
    enqueue_process(to_rq, p);

    return ERTS_MIGRATE_SUCCESS;
}
#endif /* ERTS_SMP */

Eterm
erts_process_status(Process *c_p, ErtsProcLocks c_p_locks,
		    Process *rp, Eterm rpid)
{
    Eterm res = am_undefined;
    Process *p;

    if (rp) {
	ERTS_SMP_LC_ASSERT(ERTS_PROC_LOCK_STATUS
			   & erts_proc_lc_my_proc_locks(rp));
	p = rp;
    }
    else {
	p = erts_pid2proc_opt(c_p, c_p_locks,
			      rpid, ERTS_PROC_LOCK_STATUS,
			      ERTS_P2P_FLG_ALLOW_OTHER_X);
    }

    if (p) {
	switch (p->status) {
	case P_RUNABLE:
	    res = am_runnable;
	    break;
	case P_WAITING:
	    res = am_waiting;
	    break;
	case P_RUNNING:
	    res = am_running;
	    break;
	case P_EXITING:
	    res = am_exiting;
	    break;
	case P_GARBING:
	    res = am_garbage_collecting;
	    break;
	case P_SUSPENDED:
	    res = am_suspended;
	    break;
	case P_FREE:	/* We cannot look up a process in P_FREE... */
	default:	/* Not a valid status... */
	    erl_exit(1, "Bad status (%b32u) found for process %T\n",
		     p->status, p->id);
	    break;
	}

#ifdef ERTS_SMP
	if (!rp && (p != c_p || !(ERTS_PROC_LOCK_STATUS & c_p_locks)))
	    erts_smp_proc_unlock(p, ERTS_PROC_LOCK_STATUS);
    }
    else {
	int i;
	ErtsSchedulerData *esdp;

	if (erts_common_run_queue)
	    erts_smp_runq_lock(erts_common_run_queue);
	    
	for (i = 0; i < erts_no_schedulers; i++) {
	    esdp = ERTS_SCHEDULER_IX(i);
	    if (!erts_common_run_queue)
		erts_smp_runq_lock(esdp->run_queue);
	    if (esdp->free_process && esdp->free_process->id == rpid) {
		res = am_free;
		if (!erts_common_run_queue)
		    erts_smp_runq_unlock(esdp->run_queue);
		break;
	    }
	    if (!erts_common_run_queue)
		erts_smp_runq_unlock(esdp->run_queue);
	}

	if (erts_common_run_queue)
	    erts_smp_runq_unlock(erts_common_run_queue);
#endif

    }

    return res;
}

/*
** Suspend a process 
** If we are to suspend on a port the busy_port is the thing
** otherwise busy_port is NIL
*/

void
erts_suspend(Process* process, ErtsProcLocks process_locks, Port *busy_port)
{
    ErtsRunQueue *rq;

    ERTS_SMP_LC_ASSERT(process_locks == erts_proc_lc_my_proc_locks(process));
    if (!(process_locks & ERTS_PROC_LOCK_STATUS))
	erts_smp_proc_lock(process, ERTS_PROC_LOCK_STATUS);

    rq = erts_get_runq_proc(process);

    erts_smp_runq_lock(rq);

    suspend_process(rq, process);

    erts_smp_runq_unlock(rq);

    if (busy_port)
	erts_wake_process_later(busy_port, process);

    if (!(process_locks & ERTS_PROC_LOCK_STATUS))
	erts_smp_proc_unlock(process, ERTS_PROC_LOCK_STATUS);

}

void
erts_resume(Process* process, ErtsProcLocks process_locks)
{
    ERTS_SMP_LC_ASSERT(process_locks == erts_proc_lc_my_proc_locks(process));
    if (!(process_locks & ERTS_PROC_LOCK_STATUS))
	erts_smp_proc_lock(process, ERTS_PROC_LOCK_STATUS);
    resume_process(process);
    if (!(process_locks & ERTS_PROC_LOCK_STATUS))
	erts_smp_proc_unlock(process, ERTS_PROC_LOCK_STATUS);
}

int
erts_resume_processes(ErtsProcList *plp)
{
    int nresumed = 0;
    while (plp) {
	Process *proc;
	ErtsProcList *fplp;
	ASSERT(is_internal_pid(plp->pid));
	proc = erts_pid2proc(NULL, 0, plp->pid, ERTS_PROC_LOCK_STATUS);
	if (proc) {
	    if (proclist_same(plp, proc)) {
		resume_process(proc);
		nresumed++;
	    }
	    erts_smp_proc_unlock(proc, ERTS_PROC_LOCK_STATUS);
	}
	fplp = plp;
	plp = plp->next;
	proclist_destroy(fplp);
    }
    return nresumed;
}

Eterm
erts_get_process_priority(Process *p)
{
    ErtsRunQueue *rq;
    Eterm value;
    ERTS_SMP_LC_ASSERT(ERTS_PROC_LOCK_STATUS & erts_proc_lc_my_proc_locks(p));
    rq = erts_get_runq_proc(p);
    erts_smp_runq_lock(rq);
    switch(p->prio) {
    case PRIORITY_MAX:		value = am_max;			break;
    case PRIORITY_HIGH:		value = am_high;		break;
    case PRIORITY_NORMAL:	value = am_normal;		break;
    case PRIORITY_LOW:		value = am_low;			break;
    default: ASSERT(0);		value = am_undefined;		break;
    }
    erts_smp_runq_unlock(rq);
    return value;
}

Eterm
erts_set_process_priority(Process *p, Eterm new_value)
{
    ErtsRunQueue *rq;
    Eterm old_value;
    ERTS_SMP_LC_ASSERT(ERTS_PROC_LOCK_STATUS & erts_proc_lc_my_proc_locks(p));
    rq = erts_get_runq_proc(p);
#ifdef ERTS_SMP
    ASSERT(!(p->status_flags & ERTS_PROC_SFLG_INRUNQ));
#endif
    erts_smp_runq_lock(rq);
    switch(p->prio) {
    case PRIORITY_MAX:		old_value = am_max;		break;
    case PRIORITY_HIGH:		old_value = am_high;		break;
    case PRIORITY_NORMAL:	old_value = am_normal;		break;
    case PRIORITY_LOW:		old_value = am_low;		break;
    default: ASSERT(0);		old_value = am_undefined;	break;
    }
    switch (new_value) {
    case am_max:		p->prio = PRIORITY_MAX;		break;
    case am_high:		p->prio = PRIORITY_HIGH;	break;
    case am_normal:		p->prio = PRIORITY_NORMAL;	break;
    case am_low:		p->prio = PRIORITY_LOW;		break;
    default:			old_value = THE_NON_VALUE;	break;
    }
    erts_smp_runq_unlock(rq);
    return old_value;
}

#ifdef ERTS_SMP

static ERTS_INLINE int
prepare_for_sys_schedule(void)
{
    while (!erts_port_task_have_outstanding_io_tasks()
	   && !erts_smp_atomic_xchg(&doing_sys_schedule, 1)) {
	if (!erts_port_task_have_outstanding_io_tasks())
	    return 1;
	erts_smp_atomic_set(&doing_sys_schedule, 0);
    }
    return 0;
}

#else

static ERTS_INLINE int
prepare_for_sys_schedule(void)
{
    return !erts_port_task_have_outstanding_io_tasks();
}

#endif

/* note that P_RUNNING is only set so that we don't try to remove
** running processes from the schedule queue if they exit - a running
** process not being in the schedule queue!! 
** Schedule for up to INPUT_REDUCTIONS context switches,
** return 1 if more to do.
*/

/*
 * schedule() is called from BEAM (process_main()) or HiPE
 * (hipe_mode_switch()) when the current process is to be
 * replaced by a new process. 'calls' is the number of reduction
 * steps the current process consumed.
 * schedule() returns the new process, and the new process'
 * ->fcalls field is initialised with its allowable number of
 * reduction steps.
 *
 * When no process is runnable, or when sufficiently many reduction
 * steps have been made, schedule() calls erl_sys_schedule() to
 * schedule system-level activities.
 *
 * We use the same queue for normal and low prio processes.
 * We reschedule low prio processes a certain number of times 
 * so that normal processes get to run more frequently. 
 */

Process *schedule(Process *p, int calls)
{
    ErtsRunQueue *rq;
    ErtsRunPrioQueue *rpq;
    long dt;
    ErtsSchedulerData *esdp;
    int context_reds;
    long fcalls;
    int input_reductions;
    int actual_reds;
    int reds;

    if (ERTS_USE_MODIFIED_TIMING()) {
	context_reds = ERTS_MODIFIED_TIMING_CONTEXT_REDS;
	input_reductions = ERTS_MODIFIED_TIMING_INPUT_REDS;
    }
    else {
	context_reds = CONTEXT_REDS;
	input_reductions = INPUT_REDUCTIONS;
    }

    ERTS_SMP_LC_ASSERT(!ERTS_LC_IS_BLOCKING);

    /*
     * Clean up after the process being scheduled out.
     */
    if (!p) {	/* NULL in the very first schedule() call */
	esdp = erts_get_scheduler_data();
	rq = erts_get_runq_current(esdp);
	ASSERT(esdp);
	fcalls = erts_smp_atomic_read(&function_calls);
	actual_reds = reds = 0;
	erts_smp_runq_lock(rq);
    } else {
#ifdef ERTS_SMP
	ERTS_SMP_CHK_HAVE_ONLY_MAIN_PROC_LOCK(p);
	esdp = p->scheduler_data;
	ASSERT(esdp->current_process == p
	       || esdp->free_process == p);
#else
	esdp = erts_scheduler_data;
	ASSERT(esdp->current_process == p);
#endif
	reds = actual_reds = calls - esdp->virtual_reds;
	if (reds < ERTS_PROC_MIN_CONTEXT_SWITCH_REDS_COST)
	    reds = ERTS_PROC_MIN_CONTEXT_SWITCH_REDS_COST;
	esdp->virtual_reds = 0;

	fcalls = erts_smp_atomic_addtest(&function_calls, reds);
	ASSERT(esdp && esdp == erts_get_scheduler_data());

	rq = erts_get_runq_current(esdp);

	p->reds += actual_reds;

	erts_smp_proc_lock(p, ERTS_PROC_LOCK_STATUS);

	if ((erts_system_profile_flags.runnable_procs)
	    && (p->status == P_WAITING)) {
	    profile_runnable_proc(p, am_inactive);
	}

	if (IS_TRACED(p)) {
	    switch (p->status) {
	    case P_EXITING:
		if (ARE_TRACE_FLAGS_ON(p, F_TRACE_SCHED_EXIT))
		    trace_sched(p, am_out_exiting);
		break;
	    case P_FREE:
		if (ARE_TRACE_FLAGS_ON(p, F_TRACE_SCHED_EXIT))
		    trace_sched(p, am_out_exited);
		break;
	    default:
		if (ARE_TRACE_FLAGS_ON(p, F_TRACE_SCHED))
		    trace_sched(p, am_out);
		else if (ARE_TRACE_FLAGS_ON(p, F_TRACE_SCHED_PROCS))
		    trace_virtual_sched(p, am_out);
		break;
	    }
	}	

#ifdef ERTS_SMP
	if (ERTS_PROC_PENDING_EXIT(p)) {
	    erts_handle_pending_exit(p,
				     ERTS_PROC_LOCK_MAIN|ERTS_PROC_LOCK_STATUS);
	    p->status_flags |= ERTS_PROC_SFLG_PENDADD2SCHEDQ;
	}

	if (p->pending_suspenders) {
	    handle_pending_suspend(p,
				   ERTS_PROC_LOCK_MAIN|ERTS_PROC_LOCK_STATUS);
	    ASSERT(!(p->status_flags & ERTS_PROC_SFLG_PENDADD2SCHEDQ)
		   || p->status != P_SUSPENDED);
	}
#endif
	erts_smp_runq_lock(rq);

	ERTS_PROC_REDUCTIONS_EXECUTED(rq, p->prio, reds, actual_reds);

	esdp->current_process = NULL;
#ifdef ERTS_SMP
	p->scheduler_data = NULL;
	p->runq_flags &= ~ERTS_PROC_RUNQ_FLG_RUNNING;
	p->status_flags &= ~ERTS_PROC_SFLG_RUNNING;

	if (p->status_flags & ERTS_PROC_SFLG_PENDADD2SCHEDQ) {
	    p->status_flags &= ~ERTS_PROC_SFLG_PENDADD2SCHEDQ;
	    internal_add_to_runq(rq, p);
	}
#endif


	if (p->status == P_FREE) {
#ifdef ERTS_SMP
	    ASSERT(esdp->free_process == p);
	    esdp->free_process = NULL;
	    erts_smp_proc_unlock(p, ERTS_PROC_LOCK_MAIN|ERTS_PROC_LOCK_STATUS);
	    erts_smp_proc_dec_refc(p);
#else	    
	    erts_free_proc(p);
#endif
	} else {
	    erts_smp_proc_unlock(p, ERTS_PROC_LOCK_MAIN|ERTS_PROC_LOCK_STATUS);
	}

#ifdef ERTS_SMP
	{
	    ErtsProcList *pnd_xtrs = rq->procs.pending_exiters;
	    rq->procs.pending_exiters = NULL;

	    if (pnd_xtrs) {
		erts_smp_runq_unlock(rq);
		handle_pending_exiters(pnd_xtrs);
		erts_smp_runq_lock(rq);
	    }
		
	}
	ASSERT(!esdp->free_process);
#endif
	ASSERT(!esdp->current_process);

	ERTS_SMP_CHK_NO_PROC_LOCKS;

	dt = do_time_read_and_reset();
	if (dt) {
	    erts_smp_runq_unlock(rq);
	    bump_timer(dt);
	    erts_smp_runq_lock(rq);
	}
	BM_STOP_TIMER(system);

    }

    ERTS_SMP_LC_ASSERT(!ERTS_LC_IS_BLOCKING);
 check_activities_to_run: {

#ifdef ERTS_SMP

	if (!(rq->flags & ERTS_RUNQ_FLG_SHARED_RUNQ)
	    && rq->check_balance_reds <= 0) {
	    check_balance(rq);
	}

	ERTS_SMP_LC_ASSERT(!ERTS_LC_IS_BLOCKING);
	ERTS_SMP_LC_ASSERT(erts_smp_lc_runq_is_locked(rq));

	if (rq->flags & ERTS_RUNQ_FLGS_IMMIGRATE_QMASK)
	    immigrate(rq);

 continue_check_activities_to_run:

	if (rq->flags & (ERTS_RUNQ_FLG_SHARED_RUNQ
			 | ERTS_RUNQ_FLG_CHK_CPU_BIND
			 | ERTS_RUNQ_FLG_SUSPENDED)) {
	    if ((rq->flags & ERTS_RUNQ_FLG_SUSPENDED)
		|| erts_smp_atomic_read(&esdp->suspended)) {
		suspend_scheduler(esdp);
	    }
	    if ((rq->flags & ERTS_RUNQ_FLG_CHK_CPU_BIND)
		|| erts_smp_atomic_read(&esdp->chk_cpu_bind)) {
		check_cpu_bind(esdp);
	    }
	}

#ifdef ERTS_SMP_SCHEDULERS_NEED_TO_CHECK_CHILDREN
	if (esdp->check_children) {
	    esdp->check_children = 0;
	    erts_smp_runq_unlock(rq);
	    erts_check_children();
	    erts_smp_runq_lock(rq);
	}
#endif

	erts_smp_chk_system_block(prepare_for_block,
				  resume_after_block,
				  (void *) rq);

	ERTS_SMP_LC_ASSERT(!ERTS_LC_IS_BLOCKING);
	ERTS_SMP_LC_ASSERT(erts_smp_lc_runq_is_locked(rq));

#endif

	ASSERT(rq->len == rq->procs.len + rq->ports.info.len);

#ifndef ERTS_SMP

	if (rq->len == 0 && !rq->misc.start)
	    goto do_sys_schedule;

#else /* ERTS_SMP */
	if (rq->len == 0 && !rq->misc.start) {
	    ERTS_SMP_LC_ASSERT(erts_smp_lc_runq_is_locked(rq));

	    rq->wakeup_other = 0;
	    rq->wakeup_other_reds = 0;

	    empty_runq(rq);

	    if (rq->flags & (ERTS_RUNQ_FLG_SHARED_RUNQ
			     | ERTS_RUNQ_FLG_SUSPENDED)) {
		if ((rq->flags & ERTS_RUNQ_FLG_SUSPENDED)
		    || erts_smp_atomic_read(&esdp->suspended)) {
		    non_empty_runq(rq);
		    goto continue_check_activities_to_run;
		}
	    }
	    else if (!(rq->flags & ERTS_RUNQ_FLG_INACTIVE)) {
		/*
		 * Check for ERTS_RUNQ_FLG_SUSPENDED has to be done
		 * after trying to steal a task.
		 */
		if (try_steal_task(rq)
		    || (rq->flags & ERTS_RUNQ_FLG_SUSPENDED)) {
		    non_empty_runq(rq);
		    goto continue_check_activities_to_run;
		}
	    }

	    if (prepare_for_sys_schedule()) {
		erts_smp_atomic_set(&function_calls, 0);
		fcalls = 0;
		sched_sys_wait(esdp->no, rq);
		erts_smp_atomic_set(&doing_sys_schedule, 0);
	    }
	    else {
		/* If all schedulers are waiting, one of them *should*
		   be waiting in erl_sys_schedule() */
		sched_cnd_wait(esdp->no, rq);
	    }

	    non_empty_runq(rq);

	    goto check_activities_to_run;
	}
	else
#endif /* ERTS_SMP */
	if (fcalls > input_reductions && prepare_for_sys_schedule()) {
	    int runnable;

#ifdef ERTS_SMP
	    runnable = 1;
#else
	do_sys_schedule:
	    runnable = rq->len != 0;
	    if (!runnable)
		sched_waiting_sys(esdp->no, rq);
#endif

	    /*
	     * Schedule system-level activities.
	     */

	    erts_smp_atomic_set(&function_calls, 0);
	    fcalls = 0;
	    ASSERT(!erts_port_task_have_outstanding_io_tasks());
#ifdef ERTS_SMP
	    /* erts_sys_schedule_interrupt(0); */
#endif
	    erts_smp_runq_unlock(rq);
	    erl_sys_schedule(runnable);
	    dt = do_time_read_and_reset();
	    if (dt) bump_timer(dt);
#ifdef ERTS_SMP
	    erts_smp_runq_lock(rq);
	    erts_smp_atomic_set(&doing_sys_schedule, 0);
	    goto continue_check_activities_to_run;
#else
	    if (!runnable)
		sched_active_sys(esdp->no, rq);
	    goto check_activities_to_run;
#endif
	}

	if (rq->misc.start)
	    exec_misc_ops(rq);

#ifdef ERTS_SMP
	{
	    int wo_reds = rq->wakeup_other_reds;
	    if (wo_reds) {
		if (rq->len < 2) {
		    rq->wakeup_other -= ERTS_WAKEUP_OTHER_DEC*wo_reds;
		    if (rq->wakeup_other < 0)
			rq->wakeup_other = 0;
		}
		else if (rq->wakeup_other < ERTS_WAKEUP_OTHER_LIMIT)
		    rq->wakeup_other += rq->len*wo_reds + ERTS_WAKEUP_OTHER_FIXED_INC;
		else {
		    if (erts_common_run_queue) {
			if (erts_common_run_queue->waiting)
			    wake_one_scheduler();
		    }
		    else if (erts_smp_atomic_read(&no_empty_run_queues) != 0) {
			wake_scheduler_on_empty_runq(rq);
			rq->wakeup_other = 0;
		    }
		    rq->wakeup_other = 0;
		}
	    }
	    rq->wakeup_other_reds = 0;
	}
#endif

	/*
	 * Find a new port to run.
	 */

	if (rq->ports.info.len) {
	    int have_outstanding_io;
	    have_outstanding_io = erts_port_task_execute(rq, &esdp->current_port);
	    if (have_outstanding_io && fcalls > 2*input_reductions) {
		/*
		 * If we have performed more than 2*INPUT_REDUCTIONS since
		 * last call to erl_sys_schedule() and we still haven't
		 * handled all I/O tasks we stop running processes and
		 * focus completely on ports.
		 *
		 * One could argue that this is a strange behavior. The
		 * reason for doing it this way is that it is similar
		 * to the behavior before port tasks were introduced.
		 * We don't want to change the behavior too much, at
		 * least not at the time of writing. This behavior
		 * might change in the future.
		 *
		 * /rickard
		 */
		goto check_activities_to_run;
	    }
	}

	/*
	 * Find a new process to run.
	 */
 pick_next_process:

    ERTS_DBG_CHK_PROCS_RUNQ(rq);

      switch (rq->flags & ERTS_RUNQ_FLGS_PROCS_QMASK) {
	case MAX_BIT:
	case MAX_BIT|HIGH_BIT:
	case MAX_BIT|NORMAL_BIT:
	case MAX_BIT|LOW_BIT:
	case MAX_BIT|HIGH_BIT|NORMAL_BIT:
	case MAX_BIT|HIGH_BIT|LOW_BIT:
	case MAX_BIT|NORMAL_BIT|LOW_BIT:
	case MAX_BIT|HIGH_BIT|NORMAL_BIT|LOW_BIT:
	    rpq = &rq->procs.prio[PRIORITY_MAX];
	    break;
	case HIGH_BIT:
	case HIGH_BIT|NORMAL_BIT:
	case HIGH_BIT|LOW_BIT:
	case HIGH_BIT|NORMAL_BIT|LOW_BIT:
	    rpq = &rq->procs.prio[PRIORITY_HIGH];
	    break;
        case NORMAL_BIT:
	    rpq = &rq->procs.prio[PRIORITY_NORMAL];
	    break;
        case LOW_BIT:
	    rpq = &rq->procs.prio[PRIORITY_NORMAL];
	    break;
	case NORMAL_BIT|LOW_BIT:	  
	    rpq = &rq->procs.prio[PRIORITY_NORMAL];
	    ASSERT(rpq->first != NULL);
	    p = rpq->first;
	    if (p->prio == PRIORITY_LOW) {
		if (p == rpq->last || p->skipped >= RESCHEDULE_LOW-1)
		    p->skipped = 0;
		else {
		    /* skip it */
		    p->skipped++;
		    rpq->first = p->next;
		    rpq->first->prev = NULL;
		    rpq->last->next = p;
		    p->prev = rpq->last;
		    p->next = NULL;
		    rpq->last = p;
		    goto pick_next_process;
		}
	    }
	    break;
        case 0:			/* No process at all */
	default:
	    ASSERT((rq->flags & ERTS_RUNQ_FLGS_PROCS_QMASK) == 0);
	    ASSERT(rq->procs.len == 0);
	    goto check_activities_to_run;
	}

        BM_START_TIMER(system);

	/*
	 * Take the chosen process out of the queue.
	 */
	ASSERT(rpq->first); /* Wrong qmask in rq->flags? */
	p = rpq->first;
#ifdef ERTS_SMP
	ERTS_SMP_LC_ASSERT(rq == p->run_queue);
#endif
	rpq->first = p->next;
	if (!rpq->first)
	    rpq->last = NULL;
	else
	    rpq->first->prev = NULL;

	p->next = p->prev = NULL;

	if (--rq->procs.prio_info[p->prio].len == 0)
	    rq->flags &= ~(1 << p->prio);
	ASSERT(rq->procs.len > 0);
	rq->procs.len--;
	ASSERT(rq->len > 0);
	rq->len--;

	{
	    Uint32 ee_flgs = (ERTS_RUNQ_FLG_EVACUATE(p->prio)
			      | ERTS_RUNQ_FLG_EMIGRATE(p->prio));

	    if ((rq->flags & (ERTS_RUNQ_FLG_SUSPENDED|ee_flgs)) == ee_flgs)
		ERTS_UNSET_RUNQ_FLG_EVACUATE(rq->flags, p->prio);
	}

	ERTS_DBG_CHK_PROCS_RUNQ_NOPROC(rq, p);

	rq->procs.context_switches++;

	esdp->current_process = p;

#ifdef ERTS_SMP
	p->runq_flags |= ERTS_PROC_RUNQ_FLG_RUNNING;
	erts_smp_runq_unlock(rq);

	ERTS_SMP_CHK_NO_PROC_LOCKS;

	erts_smp_proc_lock(p, ERTS_PROC_LOCK_MAIN|ERTS_PROC_LOCK_STATUS);

	if (erts_sched_stat.enabled) {
	    Uint old = ERTS_PROC_SCHED_ID(p,
					  (ERTS_PROC_LOCK_MAIN
					   | ERTS_PROC_LOCK_STATUS),
					  esdp->no);
	    int migrated = old && old != esdp->no;

	    erts_smp_spin_lock(&erts_sched_stat.lock);
	    erts_sched_stat.prio[p->prio].total_executed++;
	    erts_sched_stat.prio[p->prio].executed++;
	    if (migrated) {
		erts_sched_stat.prio[p->prio].total_migrated++;
		erts_sched_stat.prio[p->prio].migrated++;
	    }
	    erts_smp_spin_unlock(&erts_sched_stat.lock);
	}

	p->status_flags |= ERTS_PROC_SFLG_RUNNING;
	p->status_flags &= ~ERTS_PROC_SFLG_INRUNQ;
	if (ERTS_PROC_PENDING_EXIT(p)) {
	    erts_handle_pending_exit(p,
				     ERTS_PROC_LOCK_MAIN|ERTS_PROC_LOCK_STATUS);
	}
	ASSERT(!p->scheduler_data);
	p->scheduler_data = esdp;

#endif
	ASSERT(p->status != P_SUSPENDED); /* Never run a suspended process */

        ACTIVATE(p);
	reds = context_reds;

	if (IS_TRACED(p)) {
	    switch (p->status) {
	    case P_EXITING:
		if (ARE_TRACE_FLAGS_ON(p, F_TRACE_SCHED_EXIT))
		    trace_sched(p, am_in_exiting);
		break;
	    default:
		if (ARE_TRACE_FLAGS_ON(p, F_TRACE_SCHED))
		    trace_sched(p, am_in);
		else if (ARE_TRACE_FLAGS_ON(p, F_TRACE_SCHED_PROCS))
		    trace_virtual_sched(p, am_in);
		break;
	    }
	}
	if (p->status != P_EXITING)
	    p->status = P_RUNNING;

	erts_smp_proc_unlock(p, ERTS_PROC_LOCK_STATUS);

#ifdef ERTS_SMP
	if (is_not_nil(p->tracer_proc))
	    erts_check_my_tracer_proc(p);
#endif

	if ((FLAGS(p) & F_FORCE_GC) || (MSO(p).overhead > BIN_VHEAP_SZ(p))) {
	    reds -= erts_garbage_collect(p, 0, p->arg_reg, p->arity);
	    if (reds < 0) {
		reds = 1;
	    }
	}
	    
	p->fcalls = reds;
	ASSERT(IS_ACTIVE(p));
	ERTS_SMP_CHK_HAVE_ONLY_MAIN_PROC_LOCK(p);
	return p;
    }
}

void
erts_sched_stat_modify(int what)
{
    int ix;
    switch (what) {
    case ERTS_SCHED_STAT_MODIFY_ENABLE:
	erts_smp_block_system(0);
	erts_sched_stat.enabled = 1;
	erts_smp_release_system();
	break;
    case ERTS_SCHED_STAT_MODIFY_DISABLE:
	erts_smp_block_system(0);
	erts_sched_stat.enabled = 1;
	erts_smp_release_system();
	break;
    case ERTS_SCHED_STAT_MODIFY_CLEAR:
	erts_smp_spin_lock(&erts_sched_stat.lock);
	for (ix = 0; ix < ERTS_NO_PRIO_LEVELS; ix++) {
	    erts_sched_stat.prio[ix].total_executed = 0;
	    erts_sched_stat.prio[ix].executed = 0;
	    erts_sched_stat.prio[ix].total_migrated = 0;
	    erts_sched_stat.prio[ix].migrated = 0;
	}
	erts_smp_spin_unlock(&erts_sched_stat.lock);
	break;
    }
}

Eterm
erts_sched_stat_term(Process *p, int total)
{
    Uint sz;
    Uint *hp;
    Eterm prio[ERTS_NO_PRIO_LEVELS];
    Uint executed[ERTS_NO_PRIO_LEVELS];
    Uint migrated[ERTS_NO_PRIO_LEVELS];

    erts_smp_spin_lock(&erts_sched_stat.lock);
    if (total) {
	int i;
	for (i = 0; i < ERTS_NO_PRIO_LEVELS; i++) {
	    prio[i] = erts_sched_stat.prio[i].name;
	    executed[i] = erts_sched_stat.prio[i].total_executed;
	    migrated[i] = erts_sched_stat.prio[i].total_migrated;
	}
    }
    else {
	int i;
	for (i = 0; i < ERTS_NO_PRIO_LEVELS; i++) {
	    prio[i] = erts_sched_stat.prio[i].name;
	    executed[i] = erts_sched_stat.prio[i].executed;
	    erts_sched_stat.prio[i].executed = 0;
	    migrated[i] = erts_sched_stat.prio[i].migrated;
	    erts_sched_stat.prio[i].migrated = 0;
	}
    }
    erts_smp_spin_unlock(&erts_sched_stat.lock);

    sz = 0;
    (void) erts_bld_atom_2uint_3tup_list(NULL, &sz, ERTS_NO_PRIO_LEVELS,
					 prio, executed, migrated);
    hp = HAlloc(p, sz);
    return erts_bld_atom_2uint_3tup_list(&hp, NULL, ERTS_NO_PRIO_LEVELS,
					 prio, executed, migrated);
}

/*
 * Scheduling of misc stuff
 */

void
erts_schedule_misc_op(void (*func)(void *), void *arg)
{
    ErtsRunQueue *rq = erts_get_runq_current(NULL);
    ErtsMiscOpList *molp = misc_op_list_alloc();

    erts_smp_runq_lock(rq);

    while (rq->misc.evac_runq) {
	ErtsRunQueue *tmp_rq = rq->misc.evac_runq;
	erts_smp_runq_unlock(rq);
	rq = tmp_rq;
	erts_smp_runq_lock(rq);
    }

    ASSERT(!(rq->flags & ERTS_RUNQ_FLG_SUSPENDED));

    molp->next = NULL;
    molp->func = func;
    molp->arg = arg;
    if (rq->misc.end)
	rq->misc.end->next = molp;
    else
	rq->misc.start = molp;
    rq->misc.end = molp;
    smp_notify_inc_runq(rq);
    erts_smp_runq_unlock(rq);
}

static void
exec_misc_ops(ErtsRunQueue *rq)
{
    int i;
    ErtsMiscOpList *molp = rq->misc.start;
    ErtsMiscOpList *tmp_molp = molp;

    for (i = 0; i < ERTS_MAX_MISC_OPS-1; i++) {
	if (!tmp_molp) 
	    goto mtq;
	tmp_molp = tmp_molp->next;
    }
    
    if (!tmp_molp) {
    mtq:
	rq->misc.start = NULL;
	rq->misc.end = NULL;
    }
    else {
	rq->misc.start = tmp_molp->next;
	tmp_molp->next = NULL;
	if (!rq->misc.start)
	    rq->misc.end = NULL;
    }

    erts_smp_runq_unlock(rq);

    while (molp) {
	tmp_molp = molp;
	(*molp->func)(molp->arg);
	molp = molp->next;
	misc_op_list_free(tmp_molp);
    }

    erts_smp_runq_lock(rq);
}

Uint
erts_get_total_context_switches(void)
{
    Uint res = 0;
    ERTS_ATOMIC_FOREACH_RUNQ(rq, res += rq->procs.context_switches);
    return res;
}

void
erts_get_total_reductions(Uint *redsp, Uint *diffp)
{
    Uint reds = 0;
    ERTS_ATOMIC_FOREACH_RUNQ_X(rq,

			       reds += rq->procs.reductions,

			       if (redsp) *redsp = reds;
			       if (diffp) *diffp = reds - last_reductions;
			       last_reductions = reds);
}

void
erts_get_exact_total_reductions(Process *c_p, Uint *redsp, Uint *diffp)
{
    Uint reds = erts_current_reductions(c_p, c_p);
    int ix;
    erts_smp_proc_unlock(c_p, ERTS_PROC_LOCK_MAIN);
    /*
     * Wait for other schedulers to schedule out their processes
     * and update 'reductions'.
     */
    erts_smp_block_system(0);
    for (reds = 0, ix = 0; ix < erts_no_run_queues; ix++)
	reds += ERTS_RUNQ_IX(ix)->procs.reductions;
    if (redsp)
	*redsp = reds;
    if (diffp)
	*diffp = reds - last_exact_reductions;
    last_exact_reductions = reds;
    erts_smp_release_system();
    erts_smp_proc_lock(c_p, ERTS_PROC_LOCK_MAIN);
}

/*
 * erts_test_next_pid() is only used for testing.
 */
Sint
erts_test_next_pid(int set, Uint next)
{
    Sint res;
    Sint p_prev;


    erts_smp_mtx_lock(&proc_tab_mtx);

    if (!set) {
	res = p_next < 0 ? -1 : (p_serial << p_serial_shift | p_next);
    }
    else {

	p_serial = (Sint) ((next >> p_serial_shift) & p_serial_mask);
	p_next = (Sint) (erts_process_tab_index_mask & next);

	if (p_next >= erts_max_processes) {
	    p_next = 0;
	    p_serial++;
	    p_serial &= p_serial_mask;
	}

	p_prev = p_next;

	do {
	    if (!process_tab[p_next])
		break;
	    p_next++;
	    if(p_next >= erts_max_processes) {
		p_next = 0;
		p_serial++;
		p_serial &= p_serial_mask;
	    }
	} while (p_prev != p_next);

	res = process_tab[p_next] ? -1 : (p_serial << p_serial_shift | p_next);

    }

    erts_smp_mtx_unlock(&proc_tab_mtx);

    return res;

}

Uint erts_process_count(void)
{
    long res = erts_smp_atomic_read(&process_count);
    ASSERT(res >= 0);
    return (Uint) res;
}

void
erts_free_proc(Process *p)
{
#if defined(ERTS_ENABLE_LOCK_COUNT) && defined(ERTS_SMP)
    erts_lcnt_proc_lock_destroy(p);
#endif
    erts_free(ERTS_ALC_T_PROC, (void *) p);
}


/*
** Allocate process and find out where to place next process.
*/
static Process*
alloc_process(void)
{
#ifdef ERTS_SMP
    erts_pix_lock_t *pix_lock;
#endif
    Process* p;
    int p_prev;

    erts_smp_mtx_lock(&proc_tab_mtx);

    if (p_next == -1) {
	p = NULL;
	goto error; /* Process table full! */
    }

    p = (Process*) erts_alloc_fnf(ERTS_ALC_T_PROC, sizeof(Process));
    if (!p)
	goto error; /* ENOMEM */ 

    p_last = p_next;

    erts_get_emu_time(&p->started);

#ifdef ERTS_SMP
    pix_lock = ERTS_PIX2PIXLOCK(p_next);
    erts_pix_lock(pix_lock);
#endif
    ASSERT(!process_tab[p_next]);

    process_tab[p_next] = p;
    erts_smp_atomic_inc(&process_count);
    p->id = make_internal_pid(p_serial << p_serial_shift | p_next);
    if (p->id == ERTS_INVALID_PID) {
	/* Do not use the invalid pid; change serial */
	p_serial++;
	p_serial &= p_serial_mask;
	p->id = make_internal_pid(p_serial << p_serial_shift | p_next);
	ASSERT(p->id != ERTS_INVALID_PID);
    }
    ASSERT(internal_pid_serial(p->id) <= (erts_use_r9_pids_ports
					  ? ERTS_MAX_PID_R9_SERIAL
					  : ERTS_MAX_PID_SERIAL));

#ifdef ERTS_SMP
    erts_proc_lock_init(p); /* All locks locked */
    erts_pix_unlock(pix_lock);
#endif

    p->rstatus = P_FREE;
    p->rcount = 0;

    /*
     * set p_next to the next available slot
     */

    p_prev = p_next;

    while (1) {
	p_next++;
	if(p_next >= erts_max_processes) {
	    p_serial++;
	    p_serial &= p_serial_mask;
	    p_next = 0;
	}

	if (p_prev == p_next) {
	    p_next = -1;
	    break; /* Table full! */
	}

	if (!process_tab[p_next])
	    break; /* found a free slot */
    }

 error:

    erts_smp_mtx_unlock(&proc_tab_mtx);

    return p;

}

Eterm
erl_create_process(Process* parent, /* Parent of process (default group leader). */
		   Eterm mod,	/* Tagged atom for module. */
		   Eterm func,	/* Tagged atom for function. */
		   Eterm args,	/* Arguments for function (must be well-formed list). */
		   ErlSpawnOpts* so) /* Options for spawn. */
{
    ErtsRunQueue *rq;
    Process *p;
    Sint arity;			/* Number of arguments. */
#ifndef HYBRID
    Uint arg_size;		/* Size of arguments. */
#endif
    Uint sz;			/* Needed words on heap. */
    Uint heap_need;		/* Size needed on heap. */
    Eterm res = THE_NON_VALUE;

#ifdef ERTS_SMP
    erts_smp_proc_lock(parent, ERTS_PROC_LOCKS_ALL_MINOR);
#endif

#ifdef HYBRID
    /*
     * Copy the arguments to the global heap
     * Since global GC might occur we want to do this before adding the
     * new process to the process_tab.
     */
    BM_SWAP_TIMER(system,copy);
    LAZY_COPY(parent,args);
    BM_SWAP_TIMER(copy,system);
    heap_need = 0;
#endif /* HYBRID */
    /*
     * Check for errors.
     */

    if (is_not_atom(mod) || is_not_atom(func) || ((arity = list_length(args)) < 0)) {
	so->error_code = BADARG;
	goto error;
    }
    p = alloc_process(); /* All proc locks are locked by this thread
			    on success */
    if (!p) {
	erts_send_error_to_logger_str(parent->group_leader,
				      "Too many processes\n");
	so->error_code = SYSTEM_LIMIT;
	goto error;
    }

    processes_busy++;
    BM_COUNT(processes_spawned);

#ifndef HYBRID
    BM_SWAP_TIMER(system,size);
    arg_size = size_object(args);
    BM_SWAP_TIMER(size,system);
    heap_need = arg_size;
#endif

    p->flags = erts_default_process_flags;

    /* Scheduler queue mutex should be locked when changeing
     * prio. In this case we don't have to lock it, since
     * noone except us has access to the process.
     */
    if (so->flags & SPO_USE_ARGS) {
	p->min_heap_size  = so->min_heap_size;
	p->min_vheap_size = so->min_vheap_size;
	p->prio           = so->priority;
	p->max_gen_gcs    = so->max_gen_gcs;
    } else {
	p->min_heap_size  = H_MIN_SIZE;
	p->min_vheap_size = BIN_VH_MIN_SIZE;
	p->prio           = PRIORITY_NORMAL;
	p->max_gen_gcs    = (Uint16) erts_smp_atomic_read(&erts_max_gen_gcs);
    }
    p->skipped = 0;
    ASSERT(p->min_heap_size == erts_next_heap_size(p->min_heap_size, 0));
    
    p->initial[INITIAL_MOD] = mod;
    p->initial[INITIAL_FUN] = func;
    p->initial[INITIAL_ARI] = (Uint) arity;

    /*
     * Must initialize binary lists here before copying binaries to process.
     */
    p->off_heap.mso = NULL;
#ifndef HYBRID /* FIND ME! */
    p->off_heap.funs = NULL;
#endif
    p->off_heap.externals = NULL;
    p->off_heap.overhead = 0;

    heap_need +=
	IS_CONST(parent->group_leader) ? 0 : NC_HEAP_SIZE(parent->group_leader);

    if (heap_need < p->min_heap_size) {
	sz = heap_need = p->min_heap_size;
    } else {
	sz = erts_next_heap_size(heap_need, 0);
    }

#ifdef HIPE
    hipe_init_process(&p->hipe);
#ifdef ERTS_SMP
    hipe_init_process_smp(&p->hipe_smp);
#endif
#endif

    p->heap = (Eterm *) ERTS_HEAP_ALLOC(ERTS_ALC_T_HEAP, sizeof(Eterm)*sz);
    p->old_hend = p->old_htop = p->old_heap = NULL;
    p->high_water = p->heap;
#ifdef INCREMENTAL
    p->scan_top = p->high_water;
#endif
    p->gen_gcs = 0;
    p->stop = p->hend = p->heap + sz;
    p->htop = p->heap;
    p->heap_sz = sz;
    p->catches = 0;

    p->bin_vheap_sz     = p->min_vheap_size;
    p->bin_old_vheap_sz = p->min_vheap_size;
    p->bin_old_vheap    = 0;

    /* No need to initialize p->fcalls. */

    p->current = p->initial+INITIAL_MOD;

    p->i = (Eterm *) beam_apply;
    p->cp = (Eterm *) beam_apply+1;

    p->arg_reg = p->def_arg_reg;
    p->max_arg_reg = sizeof(p->def_arg_reg)/sizeof(p->def_arg_reg[0]);
    p->arg_reg[0] = mod;
    p->arg_reg[1] = func;
    BM_STOP_TIMER(system);
    BM_MESSAGE(args,p,parent);
    BM_START_TIMER(system);
#ifdef HYBRID
    p->arg_reg[2] = args;
#ifdef INCREMENTAL
    p->active = 0;
    if (ptr_val(args) >= inc_fromspc && ptr_val(args) < inc_fromend)
        INC_ACTIVATE(p);
#endif
#else
    BM_SWAP_TIMER(system,copy);
    p->arg_reg[2] = copy_struct(args, arg_size, &p->htop, &p->off_heap);
    BM_MESSAGE_COPIED(arg_size);
    BM_SWAP_TIMER(copy,system);
#endif
    p->arity = 3;

    p->fvalue = NIL;
    p->freason = EXC_NULL;
    p->ftrace = NIL;
    p->reds = 0;

#ifdef ERTS_SMP
    p->u.ptimer = NULL;
#else
    sys_memset(&p->u.tm, 0, sizeof(ErlTimer));
#endif

    p->reg = NULL;
    p->nlinks = NULL;
    p->monitors = NULL;
    p->nodes_monitors = NULL;
    p->suspend_monitors = NULL;

    ASSERT(is_pid(parent->group_leader));

    if (parent->group_leader == ERTS_INVALID_PID)
	p->group_leader = p->id;
    else {
	/* Needs to be done after the heap has been set up */
	p->group_leader =
	    IS_CONST(parent->group_leader)
	    ? parent->group_leader
	    : STORE_NC(&p->htop, &p->off_heap.externals, parent->group_leader);
    }

    erts_get_default_tracing(&p->trace_flags, &p->tracer_proc);

    p->msg.first = NULL;
    p->msg.last = &p->msg.first;
    p->msg.save = &p->msg.first;
    p->msg.len = 0;
#ifdef ERTS_SMP
    p->msg_inq.first = NULL;
    p->msg_inq.last = &p->msg_inq.first;
    p->msg_inq.len = 0;
    p->bound_runq = NULL;
#endif
    p->bif_timers = NULL;
    p->mbuf = NULL;
    p->mbuf_sz = 0;
    p->psd = NULL;
    p->dictionary = NULL;
    p->seq_trace_lastcnt = 0;
    p->seq_trace_clock = 0;
    SEQ_TRACE_TOKEN(p) = NIL;
    p->parent = parent->id == ERTS_INVALID_PID ? NIL : parent->id;

#ifdef HYBRID
    p->rrma  = NULL;
    p->rrsrc = NULL;
    p->nrr   = 0;
    p->rrsz  = 0;
#endif

    INIT_HOLE_CHECK(p);
#ifdef DEBUG
    p->last_old_htop = NULL;
#endif

    if (IS_TRACED(parent)) {
	if (parent->trace_flags & F_TRACE_SOS) {
	    p->trace_flags |= (parent->trace_flags & TRACEE_FLAGS);
	    p->tracer_proc = parent->tracer_proc;
	}
	if (ARE_TRACE_FLAGS_ON(parent, F_TRACE_PROCS)) {
	    trace_proc_spawn(parent, p->id, mod, func, args);
	}
	if (parent->trace_flags & F_TRACE_SOS1) { /* Overrides TRACE_CHILDREN */
	    p->trace_flags |= (parent->trace_flags & TRACEE_FLAGS);
	    p->tracer_proc = parent->tracer_proc;
	    p->trace_flags &= ~(F_TRACE_SOS1 | F_TRACE_SOS);
	    parent->trace_flags &= ~(F_TRACE_SOS1 | F_TRACE_SOS);
	}
    }

    /*
     * Check if this process should be initially linked to its parent.
     */

    if (so->flags & SPO_LINK) {
#ifdef DEBUG
	int ret;
#endif
	if (IS_TRACED_FL(parent, F_TRACE_PROCS)) {
	    trace_proc(parent, parent, am_link, p->id);
	}

#ifdef DEBUG
	ret = erts_add_link(&(parent->nlinks),  LINK_PID, p->id);
	ASSERT(ret == 0);
	ret = erts_add_link(&(p->nlinks), LINK_PID, parent->id);
	ASSERT(ret == 0);
#else	
	erts_add_link(&(parent->nlinks), LINK_PID, p->id);
	erts_add_link(&(p->nlinks), LINK_PID, parent->id);
#endif

	if (IS_TRACED(parent)) {
	    if (parent->trace_flags & (F_TRACE_SOL|F_TRACE_SOL1))  {
		p->trace_flags |= (parent->trace_flags & TRACEE_FLAGS);
		p->tracer_proc = parent->tracer_proc;    /* maybe steal */

		if (parent->trace_flags & F_TRACE_SOL1)  { /* maybe override */
		    p ->trace_flags &= ~(F_TRACE_SOL1 | F_TRACE_SOL);
		    parent->trace_flags &= ~(F_TRACE_SOL1 | F_TRACE_SOL);
		}
	    }
	}
    }

    /*
     * Test whether this process should be initially monitored by its parent.
     */
    if (so->flags & SPO_MONITOR) {
	Eterm mref;

	mref = erts_make_ref(parent);
	erts_add_monitor(&(parent->monitors), MON_ORIGIN, mref, p->id, NIL);
	erts_add_monitor(&(p->monitors), MON_TARGET, mref, parent->id, NIL);
	so->mref = mref;
    }

#ifdef HYBRID
    /*
     * Add process to the array of active processes.
     */
    ACTIVATE(p);
    p->active_index = erts_num_active_procs++;
    erts_active_procs[p->active_index] = p;
#endif

#ifdef ERTS_SMP
    p->scheduler_data = NULL;
    p->is_exiting = 0;
    p->status_flags = 0;
    p->runq_flags = 0;
    p->suspendee = NIL;
    p->pending_suspenders = NULL;
    p->pending_exit.reason = THE_NON_VALUE;
    p->pending_exit.bp = NULL;
#endif

#if !defined(NO_FPE_SIGNALS)
    p->fp_exception = 0;
#endif

    /*
     * Schedule process for execution.
     */

    if (!((so->flags & SPO_USE_ARGS) && so->scheduler))
	rq = erts_get_runq_proc(parent);
    else {
	int ix = so->scheduler-1;
	ASSERT(0 <= ix && ix < erts_no_run_queues);
	rq = ERTS_RUNQ_IX(ix);
	p->bound_runq = rq;
    }

    erts_smp_runq_lock(rq);

#ifdef ERTS_SMP
    p->run_queue = rq;
#endif

    p->status = P_WAITING;
    internal_add_to_runq(rq, p);

    erts_smp_runq_unlock(rq);

    res = p->id;
    erts_smp_proc_unlock(p, ERTS_PROC_LOCKS_ALL);

    VERBOSE(DEBUG_PROCESSES, ("Created a new process: %T\n",p->id));

 error:

    erts_smp_proc_unlock(parent, ERTS_PROC_LOCKS_ALL_MINOR);

    return res;
}

/*
 * Initiates a pseudo process that can be used
 * for arithmetic BIFs.
 */

void erts_init_empty_process(Process *p)
{
    p->htop = NULL;
    p->stop = NULL;
    p->hend = NULL;
    p->heap = NULL;
    p->gen_gcs = 0;
    p->max_gen_gcs = 0;
    p->min_heap_size = 0;
    p->min_vheap_size = 0;
    p->status = P_RUNABLE;
    p->gcstatus = P_RUNABLE;
    p->rstatus = P_RUNABLE;
    p->rcount = 0;
    p->id = ERTS_INVALID_PID;
    p->prio = PRIORITY_NORMAL;
    p->reds = 0;
    p->tracer_proc = NIL;
    p->trace_flags = F_INITIAL_TRACE_FLAGS;
    p->group_leader = ERTS_INVALID_PID;
    p->flags = 0;
    p->fvalue = NIL;
    p->freason = EXC_NULL;
    p->ftrace = NIL;
    p->fcalls = 0;

    p->bin_vheap_sz = BIN_VH_MIN_SIZE;
    p->bin_old_vheap_sz = BIN_VH_MIN_SIZE;
    p->bin_old_vheap = 0;
#ifdef ERTS_SMP
    p->u.ptimer = NULL;
    p->bound_runq = NULL;
#else
    memset(&(p->u.tm), 0, sizeof(ErlTimer));
#endif
    p->next = NULL;
    p->off_heap.mso = NULL;
#ifndef HYBRID /* FIND ME! */
    p->off_heap.funs = NULL;
#endif
    p->off_heap.externals = NULL;
    p->off_heap.overhead = 0;
    p->reg = NULL;
    p->heap_sz = 0;
    p->high_water = NULL;
#ifdef INCREMENTAL
    p->scan_top = NULL;
#endif
    p->old_hend = NULL;
    p->old_htop = NULL;
    p->old_heap = NULL;
    p->mbuf = NULL;
    p->mbuf_sz = 0;
    p->psd = NULL;
    p->monitors = NULL;
    p->nlinks = NULL;         /* List of links */
    p->nodes_monitors = NULL;
    p->suspend_monitors = NULL;
    p->msg.first = NULL;
    p->msg.last = &p->msg.first;
    p->msg.save = &p->msg.first;
    p->msg.len = 0;
    p->bif_timers = NULL;
    p->dictionary = NULL;
    p->seq_trace_clock = 0;
    p->seq_trace_lastcnt = 0;
    p->seq_trace_token = NIL;
    p->initial[0] = 0;
    p->initial[1] = 0;
    p->initial[2] = 0;
    p->catches = 0;
    p->cp = NULL;
    p->i = NULL;
    p->current = NULL;

    /*
     * Saved x registers.
     */
    p->arity = 0;
    p->arg_reg = NULL;
    p->max_arg_reg = 0;
    p->def_arg_reg[0] = 0;
    p->def_arg_reg[1] = 0;
    p->def_arg_reg[2] = 0;
    p->def_arg_reg[3] = 0;
    p->def_arg_reg[4] = 0;
    p->def_arg_reg[5] = 0;

    p->parent = NIL;
    p->started.tv_sec = 0;
    p->started.tv_usec = 0;

#ifdef HIPE
    hipe_init_process(&p->hipe);
#ifdef ERTS_SMP
    hipe_init_process_smp(&p->hipe_smp);
#endif
#endif

    ACTIVATE(p);

#ifdef HYBRID
    p->rrma  = NULL;
    p->rrsrc = NULL;
    p->nrr   = 0;
    p->rrsz  = 0;
#endif
    INIT_HOLE_CHECK(p);
#ifdef DEBUG
    p->last_old_htop = NULL;
#endif


#ifdef ERTS_SMP
    p->scheduler_data = NULL;
    p->is_exiting = 0;
    p->status_flags = 0;
    p->runq_flags = 0;
    p->msg_inq.first = NULL;
    p->msg_inq.last = &p->msg_inq.first;
    p->msg_inq.len = 0;
    p->suspendee = NIL;
    p->pending_suspenders = NULL;
    p->pending_exit.reason = THE_NON_VALUE;
    p->pending_exit.bp = NULL;
    erts_proc_lock_init(p);
    erts_smp_proc_unlock(p, ERTS_PROC_LOCKS_ALL);
    p->run_queue = ERTS_RUNQ_IX(0);
#endif

#if !defined(NO_FPE_SIGNALS)
    p->fp_exception = 0;
#endif

}    

#ifdef DEBUG

void
erts_debug_verify_clean_empty_process(Process* p)
{
    /* Things that erts_cleanup_empty_process() will *not* cleanup... */
    ASSERT(p->htop == NULL);
    ASSERT(p->stop == NULL);
    ASSERT(p->hend == NULL);
    ASSERT(p->heap == NULL);
    ASSERT(p->id == ERTS_INVALID_PID);
    ASSERT(p->tracer_proc == NIL);
    ASSERT(p->trace_flags == F_INITIAL_TRACE_FLAGS);
    ASSERT(p->group_leader == ERTS_INVALID_PID);
    ASSERT(p->next == NULL);
    ASSERT(p->reg == NULL);
    ASSERT(p->heap_sz == 0);
    ASSERT(p->high_water == NULL);
#ifdef INCREMENTAL
    ASSERT(p->scan_top == NULL);
#endif
    ASSERT(p->old_hend == NULL);
    ASSERT(p->old_htop == NULL);
    ASSERT(p->old_heap == NULL);

    ASSERT(p->monitors == NULL);
    ASSERT(p->nlinks == NULL);
    ASSERT(p->nodes_monitors == NULL);
    ASSERT(p->suspend_monitors == NULL);
    ASSERT(p->msg.first == NULL);
    ASSERT(p->msg.len == 0);
    ASSERT(p->bif_timers == NULL);
    ASSERT(p->dictionary == NULL);
    ASSERT(p->catches == 0);
    ASSERT(p->cp == NULL);
    ASSERT(p->i == NULL);
    ASSERT(p->current == NULL);

    ASSERT(p->parent == NIL);

#ifdef ERTS_SMP
    ASSERT(p->msg_inq.first == NULL);
    ASSERT(p->msg_inq.len == 0);
    ASSERT(p->suspendee == NIL);
    ASSERT(p->pending_suspenders == NULL);
    ASSERT(p->pending_exit.reason == THE_NON_VALUE);
    ASSERT(p->pending_exit.bp == NULL);
#endif

    /* Thing that erts_cleanup_empty_process() cleans up */

    ASSERT(p->off_heap.mso == NULL);
#ifndef HYBRID /* FIND ME! */
    ASSERT(p->off_heap.funs == NULL);
#endif
    ASSERT(p->off_heap.externals == NULL);
    ASSERT(p->off_heap.overhead == 0);

    ASSERT(p->mbuf == NULL);
}

#endif

void
erts_cleanup_empty_process(Process* p)
{
    ErlHeapFragment* mbufp;

    /* We only check fields that are known to be used... */

    erts_cleanup_offheap(&p->off_heap);
    p->off_heap.mso = NULL;
#ifndef HYBRID /* FIND ME! */
    p->off_heap.funs = NULL;
#endif
    p->off_heap.externals = NULL;
    p->off_heap.overhead = 0;

    mbufp = p->mbuf;
    while (mbufp) {
	ErlHeapFragment *next = mbufp->next;
	free_message_buffer(mbufp);
	mbufp = next;
    }
    p->mbuf = NULL;
#if defined(ERTS_ENABLE_LOCK_COUNT) && defined(ERTS_SMP)
    erts_lcnt_proc_lock_destroy(p);
#endif
#ifdef DEBUG
    erts_debug_verify_clean_empty_process(p);
#endif
}

/*
 * p must be the currently executing process.
 */
static void
delete_process(Process* p)
{
    ErlMessage* mp;
    ErlHeapFragment* bp;

    VERBOSE(DEBUG_PROCESSES, ("Removing process: %T\n",p->id));

    /* Cleanup psd */

    if (p->psd)
	erts_free(ERTS_ALC_T_PSD, p->psd);

    /* Clean binaries and funs */
    erts_cleanup_offheap(&p->off_heap);

    /*
     * The mso list should not be used anymore, but if it is, make sure that
     * we'll notice.
     */
    p->off_heap.mso = (void *) 0x8DEFFACD;

    if (p->arg_reg != p->def_arg_reg) {
	erts_free(ERTS_ALC_T_ARG_REG, p->arg_reg);
    }

    /*
     * Release heaps. Clobber contents in DEBUG build.
     */


#ifdef DEBUG
    sys_memset(p->heap, DEBUG_BAD_BYTE, p->heap_sz*sizeof(Eterm));
#endif

#ifdef HIPE
    hipe_delete_process(&p->hipe);
#endif

    ERTS_HEAP_FREE(ERTS_ALC_T_HEAP, (void*) p->heap, p->heap_sz*sizeof(Eterm));
    if (p->old_heap != NULL) {

#ifdef DEBUG
	sys_memset(p->old_heap, DEBUG_BAD_BYTE,
                   (p->old_hend-p->old_heap)*sizeof(Eterm));
#endif
	ERTS_HEAP_FREE(ERTS_ALC_T_OLD_HEAP,
		       p->old_heap,
		       (p->old_hend-p->old_heap)*sizeof(Eterm));
    }

    /*
     * Free all pending message buffers.
     */
    bp = p->mbuf;
    while (bp != NULL) {
	ErlHeapFragment* next_bp = bp->next;
	free_message_buffer(bp);
	bp = next_bp;
    }

    erts_erase_dicts(p);

    /* free all pending messages */
    mp = p->msg.first;
    while(mp != NULL) {
	ErlMessage* next_mp = mp->next;
	if (mp->data.attached) {
	    if (is_value(mp->m[0]))
		free_message_buffer(mp->data.heap_frag);
	    else {
		if (is_not_nil(mp->m[1])) {
		    ErlHeapFragment *heap_frag;
		    heap_frag = (ErlHeapFragment *) mp->data.dist_ext->ext_endp;
		    erts_cleanup_offheap(&heap_frag->off_heap);
		}
		erts_free_dist_ext_copy(mp->data.dist_ext);
	    }
	}
	free_message(mp);
	mp = next_mp;
    }

    ASSERT(!p->monitors);
    ASSERT(!p->nlinks);
    ASSERT(!p->nodes_monitors);
    ASSERT(!p->suspend_monitors);

    p->fvalue = NIL;

#ifdef HYBRID
    erts_active_procs[p->active_index] =
        erts_active_procs[--erts_num_active_procs];
    erts_active_procs[p->active_index]->active_index = p->active_index;
#ifdef INCREMENTAL
    if (INC_IS_ACTIVE(p))
         INC_DEACTIVATE(p);
#endif

    if (p->rrma != NULL) {
        erts_free(ERTS_ALC_T_ROOTSET,p->rrma);
        erts_free(ERTS_ALC_T_ROOTSET,p->rrsrc);
    }
#endif

}

static ERTS_INLINE void
set_proc_exiting(Process *p, Eterm reason, ErlHeapFragment *bp)
{
#ifdef ERTS_SMP
    erts_pix_lock_t *pix_lock = ERTS_PID2PIXLOCK(p->id);
    ERTS_SMP_LC_ASSERT(erts_proc_lc_my_proc_locks(p) == ERTS_PROC_LOCKS_ALL);
    /*
     * You are required to have all proc locks and the pix lock when going
     * to status P_EXITING. This makes it is enough to take any lock when
     * looking up a process (pid2proc()) to prevent the looked up process
     * from exiting until the lock has been released.
     */

    erts_pix_lock(pix_lock);
    p->is_exiting = 1;
#endif
    p->status = P_EXITING;
#ifdef ERTS_SMP
    erts_pix_unlock(pix_lock);
#endif
    p->fvalue = reason;
    if (bp)
	erts_link_mbuf_to_proc(p, bp);
    /*
     * We used to set freason to EXC_EXIT here, but there is no need to
     * save the stack trace since this process irreversibly is going to
     * exit.
     */
    p->freason = EXTAG_EXIT;
    KILL_CATCHES(p);
    cancel_timer(p);
    p->i = (Eterm *) beam_exit;
}


#ifdef ERTS_SMP

void
erts_handle_pending_exit(Process *c_p, ErtsProcLocks locks)
{
    ErtsProcLocks xlocks;
    ASSERT(is_value(c_p->pending_exit.reason));
    ERTS_SMP_LC_ASSERT(erts_proc_lc_my_proc_locks(c_p) == locks);
    ERTS_SMP_LC_ASSERT(locks & ERTS_PROC_LOCK_MAIN);
    ERTS_SMP_LC_ASSERT(c_p->status != P_EXITING);
    ERTS_SMP_LC_ASSERT(c_p->status != P_FREE);

    /* Ensure that all locks on c_p are locked before proceeding... */
    if (locks == ERTS_PROC_LOCKS_ALL)
	xlocks = 0;
    else {
	xlocks = ~locks & ERTS_PROC_LOCKS_ALL;
	if (erts_smp_proc_trylock(c_p, xlocks) == EBUSY) {
	    erts_smp_proc_unlock(c_p, locks & ~ERTS_PROC_LOCK_MAIN);
	    erts_smp_proc_lock(c_p, ERTS_PROC_LOCKS_ALL_MINOR);
	}
    }

    set_proc_exiting(c_p, c_p->pending_exit.reason, c_p->pending_exit.bp);
    c_p->pending_exit.reason = THE_NON_VALUE;
    c_p->pending_exit.bp = NULL;

    if (xlocks)
	erts_smp_proc_unlock(c_p, xlocks);
}

static void
handle_pending_exiters(ErtsProcList *pnd_xtrs)
{
    ErtsProcList *plp = pnd_xtrs;
    ErtsProcList *free_plp;
    while (plp) {
	Process *p = erts_pid2proc(NULL, 0, plp->pid, ERTS_PROC_LOCKS_ALL);
	if (p) {
	    if (proclist_same(plp, p)
		&& !(p->status_flags & ERTS_PROC_SFLG_RUNNING)) {
		ASSERT(p->status_flags & ERTS_PROC_SFLG_INRUNQ);
		ASSERT(ERTS_PROC_PENDING_EXIT(p));
		erts_handle_pending_exit(p, ERTS_PROC_LOCKS_ALL);
	    }
	    erts_smp_proc_unlock(p, ERTS_PROC_LOCKS_ALL);
	}
	free_plp = plp;
	plp = plp->next;
	proclist_destroy(free_plp);
    }
}

static void
save_pending_exiter(Process *p)
{
    ErtsProcList *plp;
    ErtsRunQueue *rq;

    ERTS_SMP_LC_ASSERT(ERTS_PROC_LOCK_STATUS & erts_proc_lc_my_proc_locks(p));

    rq = erts_get_runq_current(NULL);

    plp = proclist_create(p);

    erts_smp_runq_lock(rq);

    plp->next = rq->procs.pending_exiters;
    rq->procs.pending_exiters = plp;

    erts_smp_runq_unlock(rq);

}

#endif

/*
 * This function delivers an EXIT message to a process
 * which is trapping EXITs.
 */

static ERTS_INLINE void
send_exit_message(Process *to, ErtsProcLocks *to_locksp,
		  Eterm exit_term, Uint term_size, Eterm token)
{
    if (token == NIL) {
	Eterm* hp;
	Eterm mess;
	ErlHeapFragment* bp;
	ErlOffHeap *ohp;

	hp = erts_alloc_message_heap(term_size, &bp, &ohp, to, to_locksp);
	mess = copy_struct(exit_term, term_size, &hp, ohp);
	erts_queue_message(to, to_locksp, bp, mess, NIL);
    } else {
	ErlHeapFragment* bp;
	Eterm* hp;
	Eterm mess;
	Eterm temp_token;
	Uint sz_token;

	ASSERT(is_tuple(token));
	sz_token = size_object(token);
	bp = new_message_buffer(term_size+sz_token);
	hp = bp->mem;
	mess = copy_struct(exit_term, term_size, &hp, &bp->off_heap);
	/* the trace token must in this case be updated by the caller */
	seq_trace_output(token, mess, SEQ_TRACE_SEND, to->id, NULL);
	temp_token = copy_struct(token, sz_token, &hp, &bp->off_heap);
	erts_queue_message(to, to_locksp, bp, mess, temp_token);
    }
}

/*
 *
 * *** Exit signal behavior ***
 *
 * Exit signals are asynchronous (truly asynchronous in the
 * SMP emulator). When the signal is received the receiver receives an
 * 'EXIT' message if it is trapping exits; otherwise, it will either
 * ignore the signal if the exit reason is normal, or go into an
 * exiting state (status P_EXITING). When a process has gone into the
 * exiting state it will not execute any more Erlang code, but it might
 * take a while before it actually exits. The exit signal is being
 * received when the 'EXIT' message is put in the message queue, the
 * signal is dropped, or when it changes state into exiting. The time it
 * is in the exiting state before actually exiting is undefined (it
 * might take a really long time under certain conditions). The
 * receiver of the exit signal does not break links or trigger monitors
 * until it actually exits.
 *
 * Exit signals and other signals, e.g. messages, have to be received
 * by a receiver in the same order as sent by a sender.
 *
 *
 *
 * Exit signal implementation in the SMP emulator:
 *
 * If the receiver is trapping exits, the signal is transformed
 * into an 'EXIT' message and sent as a normal message, if the
 * reason is normal the signal is dropped; otherwise, the process
 * is determined to be exited. The interesting case is when the
 * process is to be exited and this is what is described below.
 *
 * If it is possible, the receiver is set in the exiting state straight
 * away and we are done; otherwise, the sender places the exit reason
 * in the pending_exit field of the process struct and if necessary
 * adds the receiver to the run queue. It is typically not possible
 * to set a scheduled process or a process which we cannot get all locks
 * on without releasing locks on it in an exiting state straight away.
 *
 * The receiver will poll the pending_exit field when it reach certain
 * places during it's execution. When it discovers the pending exit
 * it will change state into the exiting state. If the receiver wasn't
 * scheduled when the pending exit was set, the first scheduler that
 * schedules a new process will set the receiving process in the exiting
 * state just before it schedules next process.
 * 
 * When the exit signal is placed in the pending_exit field, the signal
 * is considered as being in transit on the Erlang level. The signal is
 * actually in some kind of semi transit state, since we have already
 * determined how it should be received. It will exit the process no
 * matter what if it is received (the process may exit by itself before
 * reception of the exit signal). The signal is received when it is
 * discovered in the pending_exit field by the receiver.
 *
 * The receiver have to poll the pending_exit field at least before:
 * - moving messages from the message in queue to the private message
 *   queue. This in order to preserve signal order.
 * - unlink. Otherwise the process might get exited on a link that
 *   have been removed.
 * - changing the trap_exit flag to true. This in order to simplify the
 *   implementation; otherwise, we would have to transform the signal
 *   into an 'EXIT' message when setting the trap_exit flag to true. We
 *   would also have to maintain a queue of exit signals in transit.
 * - being scheduled in or out.
 */

static ERTS_INLINE int
send_exit_signal(Process *c_p,		/* current process if and only
					   if reason is stored on it */
		 Eterm from,		/* Id of sender of signal */
		 Process *rp,		/* receiving process */
		 ErtsProcLocks *rp_locks,/* current locks on receiver */
		 Eterm reason,		/* exit reason */
		 Eterm exit_tuple,	/* Prebuild exit tuple
					   or THE_NON_VALUE */
		 Uint exit_tuple_sz,	/* Size of prebuilt exit tuple
					   (if exit_tuple != THE_NON_VALUE) */
		 Eterm token,		/* token */
		 Process *token_update, /* token updater */
		 Uint32 flags		/* flags */
    )		
{
    Eterm rsn = reason == am_kill ? am_killed : reason;

    ERTS_SMP_LC_ASSERT(*rp_locks == erts_proc_lc_my_proc_locks(rp));
    ERTS_SMP_LC_ASSERT((*rp_locks & ERTS_PROC_LOCKS_XSIG_SEND)
		       == ERTS_PROC_LOCKS_XSIG_SEND);

    ASSERT(reason != THE_NON_VALUE);

    if (ERTS_PROC_IS_TRAPPING_EXITS(rp)
	&& (reason != am_kill || (flags & ERTS_XSIG_FLG_IGN_KILL))) {
	if (is_not_nil(token) && token_update)
	    seq_trace_update_send(token_update);
	if (is_value(exit_tuple))
	    send_exit_message(rp, rp_locks, exit_tuple, exit_tuple_sz, token);
	else
	    erts_deliver_exit_message(from, rp, rp_locks, rsn, token);
	return 1; /* Receiver will get a message */
    }
    else if (reason != am_normal || (flags & ERTS_XSIG_FLG_NO_IGN_NORMAL)) {
#ifdef ERTS_SMP
	if (!ERTS_PROC_PENDING_EXIT(rp) && !rp->is_exiting) {
	    ASSERT(rp->status != P_EXITING);
	    ASSERT(rp->status != P_FREE);
	    ASSERT(!rp->pending_exit.bp);

	    if (rp == c_p && (*rp_locks & ERTS_PROC_LOCK_MAIN)) {
		/* Ensure that all locks on c_p are locked before
		   proceeding... */
		if (*rp_locks != ERTS_PROC_LOCKS_ALL) {
		    ErtsProcLocks need_locks = (~(*rp_locks)
						& ERTS_PROC_LOCKS_ALL);
		    if (erts_smp_proc_trylock(c_p, need_locks) == EBUSY) {
			erts_smp_proc_unlock(c_p,
					     *rp_locks & ~ERTS_PROC_LOCK_MAIN);
			erts_smp_proc_lock(c_p, ERTS_PROC_LOCKS_ALL_MINOR);
		    }
		    *rp_locks = ERTS_PROC_LOCKS_ALL;
		}
		set_proc_exiting(c_p, rsn, NULL);
	    }
	    else if (!(rp->status_flags & ERTS_PROC_SFLG_RUNNING)) {
		/* Process not running ... */
		ErtsProcLocks need_locks = ~(*rp_locks) & ERTS_PROC_LOCKS_ALL;
		if (need_locks
		    && erts_smp_proc_trylock(rp, need_locks) == EBUSY) {
		    /* ... but we havn't got all locks on it ... */
		    save_pending_exiter(rp);
		    /*
		     * The pending exit will be discovered when next
		     * process is scheduled in
		     */
		    goto set_pending_exit;
		}
		else {
		    /* ...and we have all locks on it... */
		    *rp_locks = ERTS_PROC_LOCKS_ALL;
		    set_proc_exiting(rp,
				     (is_immed(rsn)
				      ? rsn
				      : copy_object(rsn, rp)),
				     NULL);
		}
	    }
	    else { /* Process running... */

		/*
		 * The pending exit will be discovered when the process
		 * is scheduled out if not discovered earlier.
		 */

	    set_pending_exit:
		if (is_immed(rsn)) {
		    rp->pending_exit.reason = rsn;
		}
		else {
		    Eterm *hp;
		    Uint sz = size_object(rsn);
		    ErlHeapFragment *bp = new_message_buffer(sz);

		    hp = &bp->mem[0];
		    rp->pending_exit.reason = copy_struct(rsn,
							  sz,
							  &hp,
							  &bp->off_heap);
		    rp->pending_exit.bp = bp;
		}
		ASSERT(ERTS_PROC_PENDING_EXIT(rp));
	    }
	    if (!(rp->status_flags
		  & (ERTS_PROC_SFLG_INRUNQ|ERTS_PROC_SFLG_RUNNING)))
		erts_add_to_runq(rp);
	}
	/* else:
	 *
	 *    The receiver already has a pending exit (or is exiting)
	 *    so we drop this signal.
	 *
	 *    NOTE: dropping this exit signal is based on the assumption
	 *          that the receiver *will* exit; either on the pending
	 *          exit or by itself before seeing the pending exit.
	 */
#else /* !ERTS_SMP */
	if (c_p == rp) {
	    rp->status = P_EXITING;
	    c_p->fvalue = rsn;
	}
	else if (rp->status != P_EXITING) { /* No recursive process exits /PaN */
	    Eterm old_status = rp->status;
	    set_proc_exiting(rp,
			     is_immed(rsn) ? rsn : copy_object(rsn, rp),
			     NULL);
	    ACTIVATE(rp);
	    if (old_status != P_RUNABLE && old_status != P_RUNNING)
		erts_add_to_runq(rp);
	}
#endif
	return -1; /* Receiver will exit */
    }

    return 0; /* Receiver unaffected */
}


int
erts_send_exit_signal(Process *c_p,
		      Eterm from,
		      Process *rp,
		      ErtsProcLocks *rp_locks,
		      Eterm reason,
		      Eterm token,
		      Process *token_update,
		      Uint32 flags)
{
    return send_exit_signal(c_p,
			    from,
			    rp,
			    rp_locks,
			    reason,
			    THE_NON_VALUE,
			    0,
			    token,
			    token_update,
			    flags);
}

typedef struct {
    Eterm reason;
    Process *p;
} ExitMonitorContext;

static void doit_exit_monitor(ErtsMonitor *mon, void *vpcontext)
{
    ExitMonitorContext *pcontext = vpcontext;
    DistEntry *dep;
    ErtsMonitor *rmon;
    Process *rp;

    if (mon->type == MON_ORIGIN) {
	/* We are monitoring someone else, we need to demonitor that one.. */
	if (is_atom(mon->pid)) { /* remote by name */
	    ASSERT(is_node_name_atom(mon->pid));
	    dep = erts_sysname_to_connected_dist_entry(mon->pid);
	    if (dep) {
		erts_smp_de_links_lock(dep);
		rmon = erts_remove_monitor(&(dep->monitors), mon->ref);
		erts_smp_de_links_unlock(dep);
		if (rmon) {
		    ErtsDSigData dsd;
		    int code = erts_dsig_prepare(&dsd, dep, NULL,
						 ERTS_DSP_NO_LOCK, 0);
		    if (code == ERTS_DSIG_PREP_CONNECTED) {
			code = erts_dsig_send_demonitor(&dsd,
							rmon->pid,
							mon->name,
							mon->ref,
							1);
			ASSERT(code == ERTS_DSIG_SEND_OK);
		    }
		    erts_destroy_monitor(rmon);
		}
		erts_deref_dist_entry(dep);
	    }
	} else {
	    ASSERT(is_pid(mon->pid));
	    if (is_internal_pid(mon->pid)) { /* local by pid or name */
		rp = erts_pid2proc(NULL, 0, mon->pid, ERTS_PROC_LOCK_LINK);
		if (!rp) {
		    goto done;
		}
		rmon = erts_remove_monitor(&(rp->monitors),mon->ref);
		erts_smp_proc_unlock(rp, ERTS_PROC_LOCK_LINK);
		if (rmon == NULL) {
		    goto done;
		}
		erts_destroy_monitor(rmon);
	    } else { /* remote by pid */
		ASSERT(is_external_pid(mon->pid));
		dep = external_pid_dist_entry(mon->pid);
		ASSERT(dep != NULL);
		if (dep) {
		    erts_smp_de_links_lock(dep);
		    rmon = erts_remove_monitor(&(dep->monitors), mon->ref);
		    erts_smp_de_links_unlock(dep);
		    if (rmon) {
			ErtsDSigData dsd;
			int code = erts_dsig_prepare(&dsd, dep, NULL,
						     ERTS_DSP_NO_LOCK, 0);
			if (code == ERTS_DSIG_PREP_CONNECTED) {
			    code = erts_dsig_send_demonitor(&dsd,
							    rmon->pid,
							    mon->pid,
							    mon->ref,
							    1);
			    ASSERT(code == ERTS_DSIG_SEND_OK);
			}
			erts_destroy_monitor(rmon);
		    }
		}
	    }
	}
    } else { /* type == MON_TARGET */
	ASSERT(mon->type == MON_TARGET);
	ASSERT(is_pid(mon->pid) || is_internal_port(mon->pid));
	if (is_internal_port(mon->pid)) {
	    Port *prt = erts_id2port(mon->pid, NULL, 0);
	    if (prt == NULL) {
		goto done;
	    }
	    erts_fire_port_monitor(prt, mon->ref);
	    erts_port_release(prt); 
	} else if (is_internal_pid(mon->pid)) {/* local by name or pid */
	    Eterm watched;
	    Eterm lhp[3];
	    ErtsProcLocks rp_locks = (ERTS_PROC_LOCK_LINK
				      | ERTS_PROC_LOCKS_MSG_SEND);
	    rp = erts_pid2proc(NULL, 0, mon->pid, rp_locks);
	    if (rp == NULL) {
		goto done;
	    }
	    rmon = erts_remove_monitor(&(rp->monitors),mon->ref);
	    if (rmon) {
		erts_destroy_monitor(rmon);
		watched = (is_atom(mon->name)
			   ? TUPLE2(lhp, mon->name, 
				    erts_this_dist_entry->sysname)
			   : pcontext->p->id);
		erts_queue_monitor_message(rp, &rp_locks, mon->ref, am_process, 
					   watched, pcontext->reason);
	    }
	    /* else: demonitor while we exited, i.e. do nothing... */
	    erts_smp_proc_unlock(rp, rp_locks);
	} else { /* external by pid or name */
	    ASSERT(is_external_pid(mon->pid));    
	    dep = external_pid_dist_entry(mon->pid);
	    ASSERT(dep != NULL);
	    if (dep) {
		erts_smp_de_links_lock(dep);
		rmon = erts_remove_monitor(&(dep->monitors), mon->ref);
		erts_smp_de_links_unlock(dep);
		if (rmon) {
		    ErtsDSigData dsd;
		    int code = erts_dsig_prepare(&dsd, dep, NULL,
						 ERTS_DSP_NO_LOCK, 0);
		    if (code == ERTS_DSIG_PREP_CONNECTED) {
			code = erts_dsig_send_m_exit(&dsd,
						     mon->pid,
						     (rmon->name != NIL
						      ? rmon->name
						      : rmon->pid),
						     mon->ref,
						     pcontext->reason);
			ASSERT(code == ERTS_DSIG_SEND_OK);
		    }
		    erts_destroy_monitor(rmon);
		}
	    }
	}
    }
 done:
    /* As the monitors are previously removed from the process, 
       distribution operations will not cause monitors to disappear,
       we can safely delete it. */
       
    erts_destroy_monitor(mon);
}

typedef struct {
    Process *p;
    Eterm reason;
    Eterm exit_tuple;
    Uint exit_tuple_sz;
} ExitLinkContext;

static void doit_exit_link(ErtsLink *lnk, void *vpcontext)
{
    ExitLinkContext *pcontext = vpcontext;
    /* Unpack context, it's readonly */
    Process *p = pcontext->p;
    Eterm reason = pcontext->reason;
    Eterm exit_tuple = pcontext->exit_tuple;
    Uint exit_tuple_sz = pcontext->exit_tuple_sz;
    Eterm item = lnk->pid;
    ErtsLink *rlnk;
    DistEntry *dep;
    Process *rp;

    switch(lnk->type) {
    case LINK_PID:
	if(is_internal_port(item)) {
	    Port *prt = erts_id2port(item, NULL, 0);
	    if (prt) {
		rlnk = erts_remove_link(&prt->nlinks, p->id);
		if (rlnk)
		    erts_destroy_link(rlnk);
		erts_do_exit_port(prt, p->id, reason);
		erts_port_release(prt);
	    }
	}
	else if(is_external_port(item)) {
	    erts_dsprintf_buf_t *dsbufp = erts_create_logger_dsbuf();
	    erts_dsprintf(dsbufp,
			  "Erroneous link between %T and external port %T "
			  "found\n",
			  p->id,
			  item);
	    erts_send_error_to_logger_nogl(dsbufp);
	    ASSERT(0); /* It isn't possible to setup such a link... */
	}
	else if (is_internal_pid(item)) {
	    ErtsProcLocks rp_locks = (ERTS_PROC_LOCK_LINK
				      | ERTS_PROC_LOCKS_XSIG_SEND);
	    rp = erts_pid2proc(NULL, 0, item, rp_locks);
	    if (rp) {
		rlnk = erts_remove_link(&(rp->nlinks), p->id);
		/* If rlnk == NULL, we got unlinked while exiting,
		   i.e., do nothing... */
		if (rlnk) {
		    int xres;
		    erts_destroy_link(rlnk);
		    xres = send_exit_signal(NULL,
					    p->id,
					    rp,
					    &rp_locks, 
					    reason,
					    exit_tuple,
					    exit_tuple_sz,
					    SEQ_TRACE_TOKEN(p),
					    p,
					    ERTS_XSIG_FLG_IGN_KILL);
		    if (xres >= 0 && IS_TRACED_FL(rp, F_TRACE_PROCS)) {
			/* We didn't exit the process and it is traced */
			if (IS_TRACED_FL(rp, F_TRACE_PROCS)) {
			    trace_proc(p, rp, am_getting_unlinked, p->id);
			}
		    }
		}
		ASSERT(rp != p);
		erts_smp_proc_unlock(rp, rp_locks);
	    }
	}
	else if (is_external_pid(item)) {
	    dep = external_pid_dist_entry(item);
	    if(dep != erts_this_dist_entry) {
		ErtsDSigData dsd;
		int code;
		ErtsDistLinkData dld;
		erts_remove_dist_link(&dld, p->id, item, dep);
		erts_smp_proc_lock(p, ERTS_PROC_LOCK_MAIN);
		code = erts_dsig_prepare(&dsd, dep, p, ERTS_DSP_NO_LOCK, 0);
		if (code == ERTS_DSIG_PREP_CONNECTED) {
		    code = erts_dsig_send_exit_tt(&dsd, p->id, item, reason,
						  SEQ_TRACE_TOKEN(p));
		    ASSERT(code == ERTS_DSIG_SEND_OK);
		}
		erts_smp_proc_unlock(p, ERTS_PROC_LOCK_MAIN);
		erts_destroy_dist_link(&dld);
	    }
	}
	break;
    case LINK_NODE:
	ASSERT(is_node_name_atom(item));
	dep = erts_sysname_to_connected_dist_entry(item);
	if(dep) {
	    /* dist entries have node links in a separate structure to 
	       avoid confusion */
	    erts_smp_de_links_lock(dep);
	    rlnk = erts_remove_link(&(dep->node_links), p->id);
	    erts_smp_de_links_unlock(dep);
	    if (rlnk)
		erts_destroy_link(rlnk);
	    erts_deref_dist_entry(dep);
	} else {
#ifndef ERTS_SMP
	    /* XXX Is this possible? Shouldn't this link
	       previously have been removed if the node
	       had previously been disconnected. */
	    ASSERT(0);
#endif
	    /* This is possible when smp support has been enabled,
	       and dist port and process exits simultaneously. */
	}
	break;
	
    default:
	erl_exit(1, "bad type in link list\n");
	break;
    }
    erts_destroy_link(lnk);
}

static void
resume_suspend_monitor(ErtsSuspendMonitor *smon, void *vc_p)
{
    Process *suspendee = erts_pid2proc((Process *) vc_p, ERTS_PROC_LOCK_MAIN,
				       smon->pid, ERTS_PROC_LOCK_STATUS);
    if (suspendee) {
	if (smon->active)
	    resume_process(suspendee);
	erts_smp_proc_unlock(suspendee, ERTS_PROC_LOCK_STATUS);
    }
    erts_destroy_suspend_monitor(smon);
}

static void
continue_exit_process(Process *p
#ifdef ERTS_SMP
		      , erts_pix_lock_t *pix_lock
#endif
    );

/* this function fishishes a process and propagates exit messages - called
   by process_main when a process dies */
void 
erts_do_exit_process(Process* p, Eterm reason)
{
#ifdef ERTS_SMP
    erts_pix_lock_t *pix_lock = ERTS_PID2PIXLOCK(p->id);
#endif

    p->arity = 0;		/* No live registers */
    p->fvalue = reason;
    
#ifdef ERTS_SMP
    ERTS_SMP_CHK_HAVE_ONLY_MAIN_PROC_LOCK(p);
    /* By locking all locks (main lock is already locked) when going
       to status P_EXITING, it is enough to take any lock when
       looking up a process (erts_pid2proc()) to prevent the looked up
       process from exiting until the lock has been released. */
    erts_smp_proc_lock(p, ERTS_PROC_LOCKS_ALL_MINOR);
#endif
    
    if (erts_system_profile_flags.runnable_procs && (p->status != P_WAITING)) {
    	profile_runnable_proc(p, am_inactive);
    }

#ifdef ERTS_SMP
    erts_pix_lock(pix_lock);
    p->is_exiting = 1;
#endif
    
    p->status = P_EXITING;

#ifdef ERTS_SMP
    erts_pix_unlock(pix_lock);

    if (ERTS_PROC_PENDING_EXIT(p)) {
	/* Process exited before pending exit was received... */
	p->pending_exit.reason = THE_NON_VALUE;
	if (p->pending_exit.bp) {
	    free_message_buffer(p->pending_exit.bp);
	    p->pending_exit.bp = NULL;
	}
    }

    cancel_suspend_of_suspendee(p, ERTS_PROC_LOCKS_ALL); 

    ERTS_SMP_MSGQ_MV_INQ2PRIVQ(p);
#endif

    if (IS_TRACED_FL(p,F_TRACE_PROCS))
	trace_proc(p, p, am_exit, reason);

    erts_trace_check_exiting(p->id);

    ASSERT((p->trace_flags & F_INITIAL_TRACE_FLAGS) == F_INITIAL_TRACE_FLAGS);

    cancel_timer(p);		/* Always cancel timer just in case */

    /*
     * The timer of this process can *not* be used anymore. The field used
     * for the timer is now used for misc exiting data.
     */
    p->u.exit_data = NULL;

    if (p->bif_timers)
	erts_cancel_bif_timers(p, ERTS_PROC_LOCKS_ALL);

#ifdef ERTS_SMP
    if (p->flags & F_HAVE_BLCKD_MSCHED)
	erts_block_multi_scheduling(p, ERTS_PROC_LOCKS_ALL, 0, 1);
#endif

    erts_smp_proc_unlock(p, ERTS_PROC_LOCKS_ALL_MINOR);

#ifdef ERTS_SMP
    continue_exit_process(p, pix_lock);
#else
    continue_exit_process(p);
#endif
}

void
erts_continue_exit_process(Process *c_p)
{
#ifdef ERTS_SMP
    continue_exit_process(c_p, ERTS_PID2PIXLOCK(c_p->id));
#else
    continue_exit_process(c_p);
#endif
}

static void
continue_exit_process(Process *p
#ifdef ERTS_SMP
		      , erts_pix_lock_t *pix_lock
#endif
    )
{
    ErtsLink* lnk;
    ErtsMonitor *mon;
    ErtsProcLocks curr_locks = ERTS_PROC_LOCK_MAIN;
    Eterm reason = p->fvalue;
    DistEntry *dep;
    struct saved_calls *scb;
#ifdef DEBUG
    int yield_allowed = 1;
#endif

    ERTS_SMP_LC_ASSERT(ERTS_PROC_LOCK_MAIN == erts_proc_lc_my_proc_locks(p));

#ifdef DEBUG
    erts_smp_proc_lock(p, ERTS_PROC_LOCK_STATUS);
    ASSERT(p->status == P_EXITING);
    erts_smp_proc_unlock(p, ERTS_PROC_LOCK_STATUS);
#endif

    if (p->flags & F_USING_DB) {
	if (erts_db_process_exiting(p, ERTS_PROC_LOCK_MAIN))
	    goto yield;
	p->flags &= ~F_USING_DB;
    }

    if (p->flags & F_USING_DDLL) {
	erts_ddll_proc_dead(p, ERTS_PROC_LOCK_MAIN);
	p->flags &= ~F_USING_DDLL;
    }

    if (p->nodes_monitors) {
	erts_delete_nodes_monitors(p, ERTS_PROC_LOCK_MAIN);
	p->nodes_monitors = NULL;
    }
	

    if (p->suspend_monitors) {
	erts_sweep_suspend_monitors(p->suspend_monitors,
				    resume_suspend_monitor,
				    p);
	p->suspend_monitors = NULL;
    }

    /*
     * The registered name *should* be the last "erlang resource" to
     * cleanup.
     */
    if (p->reg) {
	(void) erts_unregister_name(p, ERTS_PROC_LOCK_MAIN, NULL, THE_NON_VALUE);
	ASSERT(!p->reg);
    }

    erts_smp_proc_lock(p, ERTS_PROC_LOCKS_ALL_MINOR);
    curr_locks = ERTS_PROC_LOCKS_ALL;

    /*
     * From this point on we are no longer allowed to yield
     * this process.
     */
#ifdef DEBUG
    yield_allowed = 0;
#endif

    {
	int pix;
	/* Do *not* use erts_get_runq_proc() */
	ErtsRunQueue *rq;
	rq = erts_get_runq_current(ERTS_GET_SCHEDULER_DATA_FROM_PROC(p));

	ASSERT(internal_pid_index(p->id) < erts_max_processes);
	pix = internal_pid_index(p->id);

	erts_smp_mtx_lock(&proc_tab_mtx);
	erts_smp_runq_lock(rq);

#ifdef ERTS_SMP
	erts_pix_lock(pix_lock);

	ASSERT(p->scheduler_data);
	ASSERT(p->scheduler_data->current_process == p);
	ASSERT(p->scheduler_data->free_process == NULL);

	p->scheduler_data->current_process = NULL;
	p->scheduler_data->free_process = p;
	p->status_flags = 0;
#endif
	process_tab[pix] = NULL; /* Time of death! */
	ASSERT(erts_smp_atomic_read(&process_count) > 0);
	erts_smp_atomic_dec(&process_count);

#ifdef ERTS_SMP
	erts_pix_unlock(pix_lock);
#endif
	erts_smp_runq_unlock(rq);

	if (p_next < 0) {
	    if (p_last >= p_next) {
		p_serial++;
		p_serial &= p_serial_mask;
	    }
	    p_next = pix;
	}

	ERTS_MAYBE_SAVE_TERMINATING_PROCESS(p);

	erts_smp_mtx_unlock(&proc_tab_mtx);
    }

    /*
     * All "erlang resources" have to be deallocated before this point,
     * e.g. registered name, so monitoring and linked processes can
     * be sure that all interesting resources have been deallocated
     * when the monitors and/or links hit.
     */

    mon = p->monitors;
    p->monitors = NULL; /* to avoid recursive deletion during traversal */

    lnk = p->nlinks;
    p->nlinks = NULL;
    p->status = P_FREE;
    dep = ((p->flags & F_DISTRIBUTION)
	   ? ERTS_PROC_SET_DIST_ENTRY(p, ERTS_PROC_LOCKS_ALL, NULL)
	   : NULL);
    scb = ERTS_PROC_SET_SAVED_CALLS_BUF(p, ERTS_PROC_LOCKS_ALL, NULL);

    erts_smp_proc_unlock(p, ERTS_PROC_LOCKS_ALL);
    processes_busy--;

    if (dep) {
	erts_do_net_exits(dep, reason);
	if(dep)
	    erts_deref_dist_entry(dep);
    }

    /*
     * Pre-build the EXIT tuple if there are any links.
     */
    if (lnk) {
	Eterm tmp_heap[4];
	Eterm exit_tuple;
	Uint exit_tuple_sz;
	Eterm* hp;

	hp = &tmp_heap[0];

	exit_tuple = TUPLE3(hp, am_EXIT, p->id, reason);

	exit_tuple_sz = size_object(exit_tuple);

	{
	    ExitLinkContext context = {p, reason, exit_tuple, exit_tuple_sz};
	    erts_sweep_links(lnk, &doit_exit_link, &context);
	}
    }

    {
	ExitMonitorContext context = {reason, p};
	erts_sweep_monitors(mon,&doit_exit_monitor,&context);
    }

    if (scb)
        erts_free(ERTS_ALC_T_CALLS_BUF, (void *) scb);

    delete_process(p);

    erts_smp_proc_lock(p, ERTS_PROC_LOCK_MAIN);
    ERTS_SMP_CHK_HAVE_ONLY_MAIN_PROC_LOCK(p);

    return;

 yield:

#ifdef DEBUG
    ASSERT(yield_allowed);
#endif

    ERTS_SMP_LC_ASSERT(curr_locks == erts_proc_lc_my_proc_locks(p));
    ERTS_SMP_LC_ASSERT(ERTS_PROC_LOCK_MAIN & curr_locks);

    ASSERT(p->status == P_EXITING);

    p->i = (Eterm *) beam_continue_exit;

    if (!(curr_locks & ERTS_PROC_LOCK_STATUS)) {
	erts_smp_proc_lock(p, ERTS_PROC_LOCK_STATUS);
	curr_locks |= ERTS_PROC_LOCK_STATUS;
    }

    erts_add_to_runq(p);

    if (curr_locks != ERTS_PROC_LOCK_MAIN)
	erts_smp_proc_unlock(p, ~ERTS_PROC_LOCK_MAIN & curr_locks);

    ERTS_SMP_LC_ASSERT(ERTS_PROC_LOCK_MAIN == erts_proc_lc_my_proc_locks(p));

}

/* Callback for process timeout */
static void
timeout_proc(Process* p)
{
    p->i = (Eterm *) p->def_arg_reg[0];
    p->flags |= F_TIMO;
    p->flags &= ~F_INSLPQUEUE;

    if (p->status == P_WAITING)
	erts_add_to_runq(p); 
    if (p->status == P_SUSPENDED)
	p->rstatus = P_RUNABLE;   /* MUST set resume status to runnable */
}


void
cancel_timer(Process* p)
{
    ERTS_SMP_LC_ASSERT(ERTS_PROC_LOCK_MAIN & erts_proc_lc_my_proc_locks(p));
    p->flags &= ~(F_INSLPQUEUE|F_TIMO);
#ifdef ERTS_SMP
    erts_cancel_smp_ptimer(p->u.ptimer);
#else
    erl_cancel_timer(&p->u.tm);
#endif
}

/*
 * Insert a process into the time queue, with a timeout 'timeout' in ms.
 */
void
set_timer(Process* p, Uint timeout)
{
    ERTS_SMP_LC_ASSERT(ERTS_PROC_LOCK_MAIN & erts_proc_lc_my_proc_locks(p));

    /* check for special case timeout=0 DONT ADD TO time queue */
    if (timeout == 0) {
	p->flags |= F_TIMO;
	return;
    }
    p->flags |= F_INSLPQUEUE;
    p->flags &= ~F_TIMO;

#ifdef ERTS_SMP
    erts_create_smp_ptimer(&p->u.ptimer,
			   p->id,
			   (ErlTimeoutProc) timeout_proc,
			   timeout);
#else
    erl_set_timer(&p->u.tm,
		  (ErlTimeoutProc) timeout_proc,
		  NULL,
		  (void*) p,
		  timeout);
#endif
}

/*
 * Stack dump functions follow.
 */

void
erts_stack_dump(int to, void *to_arg, Process *p)
{
    Eterm* sp;
    int yreg = -1;

    if (p->trace_flags & F_SENSITIVE) {
	return;
    }
    erts_program_counter_info(to, to_arg, p);
    for (sp = p->stop; sp < STACK_START(p); sp++) {
        yreg = stack_element_dump(to, to_arg, p, sp, yreg);
    }
}

void
erts_program_counter_info(int to, void *to_arg, Process *p)
{
    int i;

    erts_print(to, to_arg, "Program counter: %p (", p->i);
    print_function_from_pc(to, to_arg, p->i);
    erts_print(to, to_arg, ")\n");
    erts_print(to, to_arg, "CP: %p (", p->cp);
    print_function_from_pc(to, to_arg, p->cp);
    erts_print(to, to_arg, ")\n");
    if (!((p->status == P_RUNNING) || (p->status == P_GARBING))) {
        erts_print(to, to_arg, "arity = %d\n",p->arity);
	if (!ERTS_IS_CRASH_DUMPING) {
	    /*
	     * Only print the arguments if we are not writing a
	     * crash dump file. The arguments cannot be interpreted
	     * by the crashdump_viewer application and will therefore
	     * only cause problems.
	     */
	    for (i = 0; i < p->arity; i++)
		erts_print(to, to_arg, "   %T\n", p->arg_reg[i]);
	}
    }
}

static void
print_function_from_pc(int to, void *to_arg, Eterm* x)
{
    Eterm* addr = find_function_from_pc(x);
    if (addr == NULL) {
        if (x == beam_exit) {
            erts_print(to, to_arg, "<terminate process>");
        } else if (x == beam_continue_exit) {
            erts_print(to, to_arg, "<continue terminate process>");
        } else if (x == beam_apply+1) {
            erts_print(to, to_arg, "<terminate process normally>");
	} else if (x == 0) {
            erts_print(to, to_arg, "invalid");
        } else {
            erts_print(to, to_arg, "unknown function");
        }
    } else {
	erts_print(to, to_arg, "%T:%T/%d + %d",
		   addr[0], addr[1], addr[2], ((x-addr)-2) * sizeof(Eterm));
    }
}

static int
stack_element_dump(int to, void *to_arg, Process* p, Eterm* sp, int yreg)
{
    Eterm x = *sp;

    if (yreg < 0 || is_CP(x)) {
        erts_print(to, to_arg, "\n%p ", sp);
    } else {
        char sbuf[16];
        sprintf(sbuf, "y(%d)", yreg);
        erts_print(to, to_arg, "%-8s ", sbuf);
        yreg++;
    }

    if (is_CP(x)) {
        erts_print(to, to_arg, "Return addr %p (", (Eterm *) x);
        print_function_from_pc(to, to_arg, cp_val(x));
        erts_print(to, to_arg, ")\n");
        yreg = 0;
    } else if is_catch(x) {
        erts_print(to, to_arg, "Catch %p (", catch_pc(x));
        print_function_from_pc(to, to_arg, catch_pc(x));
        erts_print(to, to_arg, ")\n");
    } else {
	erts_print(to, to_arg, "%T\n", x);
    }
    return yreg;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
 * The processes/0 BIF implementation.                                       *
\*                                                                           */


#define ERTS_PROCESSES_BIF_TAB_INSPECT_INDICES_PER_RED 25
#define ERTS_PROCESSES_BIF_TAB_CHUNK_SIZE 1000
#define ERTS_PROCESSES_BIF_MIN_START_REDS		\
 (ERTS_PROCESSES_BIF_TAB_CHUNK_SIZE			\
  / ERTS_PROCESSES_BIF_TAB_INSPECT_INDICES_PER_RED)

#define ERTS_PROCESSES_BIF_TAB_FREE_TERM_PROC_REDS 1

#define ERTS_PROCESSES_BIF_INSPECT_TERM_PROC_PER_RED 10

#define ERTS_PROCESSES_INSPECT_TERM_PROC_MAX_REDS \
 (ERTS_PROCESSES_BIF_TAB_CHUNK_SIZE			\
  / ERTS_PROCESSES_BIF_TAB_INSPECT_INDICES_PER_RED)
 

#define ERTS_PROCESSES_BIF_BUILD_RESULT_CONSES_PER_RED 75

#define ERTS_PROCS_DBG_DO_TRACE 0

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

#define ERTS_PROCS_DBGLVL_CHK_HALLOC 1
#define ERTS_PROCS_DBGLVL_CHK_FOUND_PIDS 5
#define ERTS_PROCS_DBGLVL_CHK_PIDS 10
#define ERTS_PROCS_DBGLVL_CHK_TERM_PROC_LIST 20
#define ERTS_PROCS_DBGLVL_CHK_RESLIST 20

#if ERTS_PROCESSES_BIF_DEBUGLEVEL == 0
#  define ERTS_PROCS_ASSERT(EXP)
#else
#  define ERTS_PROCS_ASSERT(EXP) \
    ((void) ((EXP) \
	     ? 1 \
	     : (debug_processes_assert_error(#EXP, __FILE__, __LINE__), 0)))
#endif


#if ERTS_PROCESSES_BIF_DEBUGLEVEL >=  ERTS_PROCS_DBGLVL_CHK_HALLOC
#  define ERTS_PROCS_DBG_SAVE_HEAP_ALLOC(PBDP, HP, SZ)			\
do {									\
    ERTS_PROCS_ASSERT(!(PBDP)->debug.heap);				\
    ERTS_PROCS_ASSERT(!(PBDP)->debug.heap_size);			\
    (PBDP)->debug.heap = (HP);						\
    (PBDP)->debug.heap_size = (SZ);					\
} while (0)
#  define ERTS_PROCS_DBG_VERIFY_HEAP_ALLOC_USED(PBDP, HP)		\
do {									\
    ERTS_PROCS_ASSERT((PBDP)->debug.heap);				\
    ERTS_PROCS_ASSERT((PBDP)->debug.heap_size);				\
    ERTS_PROCS_ASSERT((PBDP)->debug.heap + (PBDP)->debug.heap_size == (HP));\
    (PBDP)->debug.heap = NULL;						\
    (PBDP)->debug.heap_size = 0;					\
} while (0)
#  define ERTS_PROCS_DBG_HEAP_ALLOC_INIT(PBDP)				\
do {									\
    (PBDP)->debug.heap = NULL;						\
    (PBDP)->debug.heap_size = 0;					\
} while (0)
#else
#  define ERTS_PROCS_DBG_SAVE_HEAP_ALLOC(PBDP, HP, SZ)
#  define ERTS_PROCS_DBG_VERIFY_HEAP_ALLOC_USED(PBDP, HP)
#  define ERTS_PROCS_DBG_HEAP_ALLOC_INIT(PBDP)
#endif

#if ERTS_PROCESSES_BIF_DEBUGLEVEL >= ERTS_PROCS_DBGLVL_CHK_RESLIST
#  define ERTS_PROCS_DBG_CHK_RESLIST(R) debug_processes_check_res_list((R))
#else
#  define ERTS_PROCS_DBG_CHK_RESLIST(R)
#endif

#if ERTS_PROCESSES_BIF_DEBUGLEVEL >= ERTS_PROCS_DBGLVL_CHK_PIDS
#  define ERTS_PROCS_DBG_SAVE_PIDS(PBDP) debug_processes_save_all_pids((PBDP))
#  define ERTS_PROCS_DBG_VERIFY_PIDS(PBDP)		\
do {							\
    if (!(PBDP)->debug.correct_pids_verified)		\
	debug_processes_verify_all_pids((PBDP));	\
} while (0)
#  define ERTS_PROCS_DBG_CLEANUP_CHK_PIDS(PBDP)		\
do {							\
    if ((PBDP)->debug.correct_pids) {			\
	erts_free(ERTS_ALC_T_PROCS_PIDS,		\
		  (PBDP)->debug.correct_pids);		\
	(PBDP)->debug.correct_pids = NULL;		\
    }							\
} while(0)
#  define ERTS_PROCS_DBG_CHK_PIDS_INIT(PBDP)		\
do {							\
    (PBDP)->debug.correct_pids_verified = 0;		\
    (PBDP)->debug.correct_pids = NULL;			\
} while (0)
#else
#  define ERTS_PROCS_DBG_SAVE_PIDS(PBDP)
#  define ERTS_PROCS_DBG_VERIFY_PIDS(PBDP)
#  define ERTS_PROCS_DBG_CLEANUP_CHK_PIDS(PBDP)
#  define ERTS_PROCS_DBG_CHK_PIDS_INIT(PBDP)
#endif

#if ERTS_PROCESSES_BIF_DEBUGLEVEL >= ERTS_PROCS_DBGLVL_CHK_FOUND_PIDS
#  define ERTS_PROCS_DBG_CHK_PID_FOUND(PBDP, PID, TVP) \
  debug_processes_check_found_pid((PBDP), (PID), (TVP), 1)
#  define ERTS_PROCS_DBG_CHK_PID_NOT_FOUND(PBDP, PID, TVP) \
  debug_processes_check_found_pid((PBDP), (PID), (TVP), 0)
#else
#  define ERTS_PROCS_DBG_CHK_PID_FOUND(PBDP, PID, TVP)
#  define ERTS_PROCS_DBG_CHK_PID_NOT_FOUND(PBDP, PID, TVP)
#endif

#if ERTS_PROCESSES_BIF_DEBUGLEVEL >= ERTS_PROCS_DBGLVL_CHK_TERM_PROC_LIST
#  define ERTS_PROCS_DBG_CHK_TPLIST() \
  debug_processes_check_term_proc_list()
#  define ERTS_PROCS_DBG_CHK_FREELIST(FL) \
  debug_processes_check_term_proc_free_list(FL)
#else
#  define ERTS_PROCS_DBG_CHK_TPLIST()
#  define ERTS_PROCS_DBG_CHK_FREELIST(FL)
#endif

#if ERTS_PROCESSES_BIF_DEBUGLEVEL == 0
#if ERTS_PROCS_DBG_DO_TRACE
#    define ERTS_PROCS_DBG_INIT(P, PBDP) (PBDP)->debug.caller = (P)->id
#  else
#    define ERTS_PROCS_DBG_INIT(P, PBDP)
#  endif
#  define ERTS_PROCS_DBG_CLEANUP(PBDP)
#else
#  define ERTS_PROCS_DBG_INIT(P, PBDP)			\
do {							\
    (PBDP)->debug.caller = (P)->id;			\
    ERTS_PROCS_DBG_HEAP_ALLOC_INIT((PBDP));		\
    ERTS_PROCS_DBG_CHK_PIDS_INIT((PBDP));		\
} while (0)
#  define ERTS_PROCS_DBG_CLEANUP(PBDP)			\
do {							\
    ERTS_PROCS_DBG_CLEANUP_CHK_PIDS((PBDP));		\
} while (0)
#endif

#if ERTS_PROCS_DBG_DO_TRACE
#  define ERTS_PROCS_DBG_TRACE(PID, FUNC, WHAT)			\
     erts_fprintf(stderr, "%T %s:%d:%s(): %s\n",		\
		  (PID), __FILE__, __LINE__, #FUNC, #WHAT)
#else
#  define ERTS_PROCS_DBG_TRACE(PID, FUNC, WHAT)
#endif

static Uint processes_bif_tab_chunks;
static Export processes_trap_export;

typedef struct {
    SysTimeval time;
} ErtsProcessesBifChunkInfo;

typedef enum {
    INITIALIZING,
    INSPECTING_TABLE,
    INSPECTING_TERMINATED_PROCESSES,
    BUILDING_RESULT,
    RETURN_RESULT
} ErtsProcessesBifState;

typedef struct {
    ErtsProcessesBifState state;
    Eterm caller;
    ErtsProcessesBifChunkInfo *chunk;
    int tix;
    int pid_ix;
    int pid_sz;
    Eterm *pid;
    ErtsTermProcElement *bif_invocation; /* Only used when > 1 chunk */

#if ERTS_PROCESSES_BIF_DEBUGLEVEL != 0 || ERTS_PROCS_DBG_DO_TRACE
    struct {
	Eterm caller;
#if ERTS_PROCESSES_BIF_DEBUGLEVEL >= ERTS_PROCS_DBGLVL_CHK_FOUND_PIDS
	SysTimeval *pid_started;
#endif
#if ERTS_PROCESSES_BIF_DEBUGLEVEL >= ERTS_PROCS_DBGLVL_CHK_HALLOC
	Eterm *heap;
	Uint heap_size;
#endif
#if ERTS_PROCESSES_BIF_DEBUGLEVEL >= ERTS_PROCS_DBGLVL_CHK_PIDS
	int correct_pids_verified;
	Eterm *correct_pids;
#endif
    } debug;
#endif

} ErtsProcessesBifData;


#if ERTS_PROCESSES_BIF_DEBUGLEVEL != 0
static void debug_processes_assert_error(char* expr, char* file, int line);
#endif
#if ERTS_PROCESSES_BIF_DEBUGLEVEL >= ERTS_PROCS_DBGLVL_CHK_RESLIST
static void debug_processes_check_res_list(Eterm list);
#endif
#if ERTS_PROCESSES_BIF_DEBUGLEVEL >= ERTS_PROCS_DBGLVL_CHK_PIDS
static void debug_processes_save_all_pids(ErtsProcessesBifData *pbdp);
static void debug_processes_verify_all_pids(ErtsProcessesBifData *pbdp);
#endif
#if ERTS_PROCESSES_BIF_DEBUGLEVEL >= ERTS_PROCS_DBGLVL_CHK_FOUND_PIDS
static void debug_processes_check_found_pid(ErtsProcessesBifData *pbdp,
					    Eterm pid,
					    SysTimeval *started,
					    int pid_should_be_found);
#endif
#if ERTS_PROCESSES_BIF_DEBUGLEVEL >= ERTS_PROCS_DBGLVL_CHK_TERM_PROC_LIST
static SysTimeval debug_tv_start;
static void debug_processes_check_term_proc_list(void);
static void debug_processes_check_term_proc_free_list(ErtsTermProcElement *tpep);
#endif

static void
save_terminating_process(Process *p)
{
    ErtsTermProcElement *tpep = erts_alloc(ERTS_ALC_T_PROCS_TPROC_EL,
					   sizeof(ErtsTermProcElement));
    ERTS_PROCS_ASSERT(saved_term_procs.start && saved_term_procs.end);
    ERTS_SMP_LC_ASSERT(erts_lc_mtx_is_locked(&proc_tab_mtx));

    ERTS_PROCS_DBG_CHK_TPLIST();

    tpep->prev = saved_term_procs.end;
    tpep->next = NULL;
    tpep->ix = internal_pid_index(p->id);
    tpep->u.process.pid = p->id;
    tpep->u.process.spawned = p->started;
    erts_get_emu_time(&tpep->u.process.exited);

    saved_term_procs.end->next = tpep;
    saved_term_procs.end = tpep;

    ERTS_PROCS_DBG_CHK_TPLIST();

    ERTS_PROCS_ASSERT((tpep->prev->ix >= 0
		       ? erts_cmp_timeval(&tpep->u.process.exited,
					  &tpep->prev->u.process.exited)
		       : erts_cmp_timeval(&tpep->u.process.exited,
					  &tpep->prev->u.bif_invocation.time)) > 0);
}

static void
cleanup_processes_bif_data(Binary *bp)
{
    ErtsProcessesBifData *pbdp = ERTS_MAGIC_BIN_DATA(bp);

    ERTS_PROCS_DBG_TRACE(pbdp->debug.caller, cleanup_processes_bif_data, call);

    if (pbdp->state != INITIALIZING) {

	if (pbdp->chunk) {
	    erts_free(ERTS_ALC_T_PROCS_CNKINF, pbdp->chunk);
	    pbdp->chunk = NULL;
	}
	if (pbdp->pid) {
	    erts_free(ERTS_ALC_T_PROCS_PIDS, pbdp->pid);
	    pbdp->pid = NULL;
	}

#if ERTS_PROCESSES_BIF_DEBUGLEVEL >= ERTS_PROCS_DBGLVL_CHK_FOUND_PIDS
	if (pbdp->debug.pid_started) {
	    erts_free(ERTS_ALC_T_PROCS_PIDS, pbdp->debug.pid_started);
	    pbdp->debug.pid_started = NULL;
	}
#endif

	if (pbdp->bif_invocation) {
	    ErtsTermProcElement *tpep;

	    erts_smp_mtx_lock(&proc_tab_mtx);

	    ERTS_PROCS_DBG_TRACE(pbdp->debug.caller,
				 cleanup_processes_bif_data,
				 term_proc_cleanup);

	    tpep = pbdp->bif_invocation;
	    pbdp->bif_invocation = NULL;

	    ERTS_PROCS_DBG_CHK_TPLIST();

	    if (tpep->prev) {
		/*
		 * Only remove this bif invokation when we
		 * have preceding invokations.
		 */
		tpep->prev->next = tpep->next;
		if (tpep->next)
		    tpep->next->prev = tpep->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);
		    saved_term_procs.end = tpep->prev;
		}
		erts_free(ERTS_ALC_T_PROCS_TPROC_EL, tpep);
	    }
	    else {
		/*
		 * Free all elements until next bif invokation
		 * is found.
		 */
		ERTS_PROCS_ASSERT(saved_term_procs.start == tpep);
		do {
		    ErtsTermProcElement *ftpep = tpep;
		    tpep = tpep->next;
		    erts_free(ERTS_ALC_T_PROCS_TPROC_EL, ftpep);
		} while (tpep && tpep->ix >= 0);
		saved_term_procs.start = tpep;
		if (tpep)
		    tpep->prev = NULL;
		else
		    saved_term_procs.end = NULL;
	    }

	    ERTS_PROCS_DBG_CHK_TPLIST();

	    erts_smp_mtx_unlock(&proc_tab_mtx);

	}
    }

    ERTS_PROCS_DBG_TRACE(pbdp->debug.caller,
			 cleanup_processes_bif_data,
			 return);
    ERTS_PROCS_DBG_CLEANUP(pbdp);
}

static int
processes_bif_engine(Process *p, Eterm *res_accp, Binary *mbp)
{
    ErtsProcessesBifData *pbdp = ERTS_MAGIC_BIN_DATA(mbp);
    int have_reds;
    int reds;
    int locked = 0;

    do {
	switch (pbdp->state) {
	case INITIALIZING:
	    pbdp->chunk = erts_alloc(ERTS_ALC_T_PROCS_CNKINF,
				     (sizeof(ErtsProcessesBifChunkInfo)
				      * processes_bif_tab_chunks));
	    pbdp->tix = 0;
	    pbdp->pid_ix = 0;

	    erts_smp_mtx_lock(&proc_tab_mtx);
	    locked = 1;

	    ERTS_PROCS_DBG_TRACE(p->id, processes_bif_engine, init);

	    pbdp->pid_sz = erts_process_count();
	    pbdp->pid = erts_alloc(ERTS_ALC_T_PROCS_PIDS,
				   sizeof(Eterm)*pbdp->pid_sz);

#if ERTS_PROCESSES_BIF_DEBUGLEVEL >= ERTS_PROCS_DBGLVL_CHK_FOUND_PIDS
	    pbdp->debug.pid_started = erts_alloc(ERTS_ALC_T_PROCS_PIDS,
					   sizeof(SysTimeval)*pbdp->pid_sz);
#endif

	    ERTS_PROCS_DBG_SAVE_PIDS(pbdp);

	    if (processes_bif_tab_chunks == 1)
		pbdp->bif_invocation = NULL;
	    else {
		/*
		 * We will have to access the table multiple times
		 * releasing the table lock in between chunks.
		 */
		pbdp->bif_invocation = erts_alloc(ERTS_ALC_T_PROCS_TPROC_EL,
						  sizeof(ErtsTermProcElement));
		pbdp->bif_invocation->ix = -1;
		erts_get_emu_time(&pbdp->bif_invocation->u.bif_invocation.time);
		ERTS_PROCS_DBG_CHK_TPLIST();

		pbdp->bif_invocation->next = NULL;
		if (saved_term_procs.end) {
		    pbdp->bif_invocation->prev = saved_term_procs.end;
		    saved_term_procs.end->next = pbdp->bif_invocation;
		    ERTS_PROCS_ASSERT(saved_term_procs.start);
		}
		else {
		    pbdp->bif_invocation->prev = NULL;
		    saved_term_procs.start = pbdp->bif_invocation;
		}
		saved_term_procs.end = pbdp->bif_invocation;

		ERTS_PROCS_DBG_CHK_TPLIST();

	    }

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

	case INSPECTING_TABLE: {
	    int ix = pbdp->tix;
	    int indices = ERTS_PROCESSES_BIF_TAB_CHUNK_SIZE;
	    int cix = ix / ERTS_PROCESSES_BIF_TAB_CHUNK_SIZE;
	    int end_ix = ix + indices;
	    SysTimeval *invocation_timep;

	    invocation_timep = (pbdp->bif_invocation
				? &pbdp->bif_invocation->u.bif_invocation.time
				: NULL);

	    ERTS_PROCS_ASSERT(is_nil(*res_accp));
	    if (!locked) {
		erts_smp_mtx_lock(&proc_tab_mtx);
		locked = 1;
	    }

	    ERTS_SMP_LC_ASSERT(erts_lc_mtx_is_locked(&proc_tab_mtx));
	    ERTS_PROCS_DBG_TRACE(p->id, processes_bif_engine, insp_table);

	    if (cix != 0)
		erts_get_emu_time(&pbdp->chunk[cix].time);
	    else if (pbdp->bif_invocation)
		pbdp->chunk[0].time = *invocation_timep;
	    /* else: Time is irrelevant */

	    if (end_ix >= erts_max_processes) {
		ERTS_PROCS_ASSERT(cix+1 == processes_bif_tab_chunks);
		end_ix = erts_max_processes;
		indices = end_ix - ix;
		/* What to do when done with this chunk */
		pbdp->state = (processes_bif_tab_chunks == 1
			       ? BUILDING_RESULT
			       : INSPECTING_TERMINATED_PROCESSES);
	    }
    
	    for (; ix < end_ix; ix++) {
		Process *rp = process_tab[ix];
		if (rp
		    && (!invocation_timep
			|| erts_cmp_timeval(&rp->started,
					    invocation_timep) < 0)) {
		    ERTS_PROCS_ASSERT(is_internal_pid(rp->id));
		    pbdp->pid[pbdp->pid_ix] = rp->id;

#if ERTS_PROCESSES_BIF_DEBUGLEVEL >= ERTS_PROCS_DBGLVL_CHK_FOUND_PIDS
		    pbdp->debug.pid_started[pbdp->pid_ix] = rp->started;
#endif

		    pbdp->pid_ix++;
		    ERTS_PROCS_ASSERT(pbdp->pid_ix <= pbdp->pid_sz);
		}
	    }

	    pbdp->tix = end_ix;
	    
	    erts_smp_mtx_unlock(&proc_tab_mtx);
	    locked = 0;

	    reds = indices/ERTS_PROCESSES_BIF_TAB_INSPECT_INDICES_PER_RED;
	    BUMP_REDS(p, reds);

	    have_reds = ERTS_BIF_REDS_LEFT(p);

	    if (have_reds && pbdp->state == INSPECTING_TABLE) {
		ix = pbdp->tix;
		indices = ERTS_PROCESSES_BIF_TAB_CHUNK_SIZE;
		end_ix = ix + indices;
		if (end_ix > erts_max_processes) {
		    end_ix = erts_max_processes;
		    indices = end_ix - ix;
		}
		
		reds = indices/ERTS_PROCESSES_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_TERMINATED_PROCESSES: {
	    int i;
	    int max_reds;
	    int free_term_procs = 0;
	    SysTimeval *invocation_timep;
	    ErtsTermProcElement *tpep;
	    ErtsTermProcElement *free_list = NULL;

	    tpep = pbdp->bif_invocation;
	    ERTS_PROCS_ASSERT(tpep);
	    invocation_timep = &tpep->u.bif_invocation.time;

	    max_reds = have_reds = ERTS_BIF_REDS_LEFT(p);
	    if (max_reds > ERTS_PROCESSES_INSPECT_TERM_PROC_MAX_REDS)
		max_reds = ERTS_PROCESSES_INSPECT_TERM_PROC_MAX_REDS;

	    reds = 0;
	    erts_smp_mtx_lock(&proc_tab_mtx);
	    ERTS_PROCS_DBG_TRACE(p->id, processes_bif_engine, insp_term_procs);

	    ERTS_PROCS_DBG_CHK_TPLIST();

	    if (tpep->prev)
		tpep->prev->next = tpep->next;
	    else {
		ERTS_PROCS_ASSERT(saved_term_procs.start == tpep);
		saved_term_procs.start = tpep->next;

		if (saved_term_procs.start && saved_term_procs.start->ix >= 0) {
		    free_list = saved_term_procs.start;
		    free_term_procs = 1;
		}
	    }

	    if (tpep->next)
		tpep->next->prev = tpep->prev;
	    else
		saved_term_procs.end = tpep->prev;

	    tpep = tpep->next;

	    i = 0;
	    while (reds < max_reds && tpep) {
		if (tpep->ix < 0) {
		    if (free_term_procs) {
			ERTS_PROCS_ASSERT(free_list);
			ERTS_PROCS_ASSERT(tpep->prev);

			tpep->prev->next = NULL; /* end of free_list */
			saved_term_procs.start = tpep;
			tpep->prev = NULL;
			free_term_procs = 0;
		    }
		}
		else {
		    int cix = tpep->ix/ERTS_PROCESSES_BIF_TAB_CHUNK_SIZE;
		    SysTimeval *chunk_timep = &pbdp->chunk[cix].time;
		    Eterm pid = tpep->u.process.pid;
		    ERTS_PROCS_ASSERT(is_internal_pid(pid));

		    if (erts_cmp_timeval(&tpep->u.process.spawned,
					 invocation_timep) < 0) {
			if (erts_cmp_timeval(&tpep->u.process.exited,
					     chunk_timep) < 0) {
			    ERTS_PROCS_DBG_CHK_PID_NOT_FOUND(pbdp,
							     pid,
							     &tpep->u.process.spawned);
			    pbdp->pid[pbdp->pid_ix] = pid;
#if ERTS_PROCESSES_BIF_DEBUGLEVEL >= ERTS_PROCS_DBGLVL_CHK_FOUND_PIDS
			    pbdp->debug.pid_started[pbdp->pid_ix] = tpep->u.process.spawned;
#endif
			    pbdp->pid_ix++;
			    ERTS_PROCS_ASSERT(pbdp->pid_ix <= pbdp->pid_sz);
			}
			else {
			    ERTS_PROCS_DBG_CHK_PID_FOUND(pbdp,
							 pid,
							 &tpep->u.process.spawned);
			}
		    }
		    else {
			ERTS_PROCS_DBG_CHK_PID_NOT_FOUND(pbdp,
							 pid,
							 &tpep->u.process.spawned);
		    }

		    i++;
		    if (i == ERTS_PROCESSES_BIF_INSPECT_TERM_PROC_PER_RED) {
			reds++;
			i = 0;
		    }
		    if (free_term_procs)
			reds += ERTS_PROCESSES_BIF_TAB_FREE_TERM_PROC_REDS;
		}
		tpep = tpep->next;
	    }

	    if (free_term_procs) {
 		ERTS_PROCS_ASSERT(free_list);
		saved_term_procs.start = tpep;
		if (!tpep)
		    saved_term_procs.end = NULL;
		else {
		    ERTS_PROCS_ASSERT(tpep->prev);
		    tpep->prev->next = NULL; /* end of free_list */
		    tpep->prev = NULL;
		}
	    }

	    if (!tpep) {
		/* Done */
		ERTS_PROCS_ASSERT(pbdp->pid_ix == pbdp->pid_sz);
		pbdp->state = BUILDING_RESULT;
		pbdp->bif_invocation->next = free_list;
		free_list = pbdp->bif_invocation;
		pbdp->bif_invocation = NULL;
	    }
	    else {
		/* Link in bif_invocation again where we left off */
		pbdp->bif_invocation->prev = tpep->prev;
		pbdp->bif_invocation->next = tpep;
		tpep->prev = pbdp->bif_invocation;
		if (pbdp->bif_invocation->prev)
		    pbdp->bif_invocation->prev->next = pbdp->bif_invocation;
		else {
		    ERTS_PROCS_ASSERT(saved_term_procs.start == tpep);
		    saved_term_procs.start = pbdp->bif_invocation;
		}
	    }

	    ERTS_PROCS_DBG_CHK_TPLIST();
	    ERTS_PROCS_DBG_CHK_FREELIST(free_list);
	    erts_smp_mtx_unlock(&proc_tab_mtx);

	    /*
	     * We do the actual free of term proc 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) {
		tpep = free_list;
		free_list = tpep->next;
		erts_free(ERTS_ALC_T_PROCS_TPROC_EL, tpep);
	    }

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

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

	    ERTS_PROCS_DBG_VERIFY_PIDS(pbdp);
	    ERTS_PROCS_DBG_CHK_RESLIST(res);

	    ERTS_PROCS_DBG_TRACE(p->id, processes_bif_engine, begin_build_res);

	    have_reds = ERTS_BIF_REDS_LEFT(p);
	    conses = ERTS_PROCESSES_BIF_BUILD_RESULT_CONSES_PER_RED*have_reds;
	    min_ix = pbdp->pid_ix - conses;
	    if (min_ix < 0) {
		min_ix = 0;
		conses = pbdp->pid_ix;
	    }

	    hp = HAlloc(p, conses*2);
	    ERTS_PROCS_DBG_SAVE_HEAP_ALLOC(pbdp, hp, conses*2);

	    for (ix = pbdp->pid_ix - 1; ix >= min_ix; ix--) {
		ERTS_PROCS_ASSERT(is_internal_pid(pbdp->pid[ix]));
		res = CONS(hp, pbdp->pid[ix], res);
		hp += 2;
	    }

	    ERTS_PROCS_DBG_VERIFY_HEAP_ALLOC_USED(pbdp, hp);

	    pbdp->pid_ix = min_ix;
	    if (min_ix == 0)
		pbdp->state = RETURN_RESULT;
	    else {
		pbdp->pid_sz = min_ix;
		pbdp->pid = erts_realloc(ERTS_ALC_T_PROCS_PIDS,
					 pbdp->pid,
					 sizeof(Eterm)*pbdp->pid_sz);
#if ERTS_PROCESSES_BIF_DEBUGLEVEL >= ERTS_PROCS_DBGLVL_CHK_FOUND_PIDS
		pbdp->debug.pid_started = erts_realloc(ERTS_ALC_T_PROCS_PIDS,
						 pbdp->debug.pid_started,
						 sizeof(SysTimeval)*pbdp->pid_sz);
#endif
	    }
	    reds = conses/ERTS_PROCESSES_BIF_BUILD_RESULT_CONSES_PER_RED;
	    BUMP_REDS(p, reds);
	    have_reds -= reds;

	    ERTS_PROCS_DBG_CHK_RESLIST(res);
	    ERTS_PROCS_DBG_TRACE(p->id, processes_bif_engine, end_build_res);
	    *res_accp = res;
	    break;
	}
	case RETURN_RESULT:
	    cleanup_processes_bif_data(mbp);
	    return 1;

	default:
	    erl_exit(ERTS_ABORT_EXIT,
		     "erlang:processes/0: Invalid state: %d\n",
		     (int) pbdp->state);
	}

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

    return 0;
}

/*
 * processes_trap/2 is a hidden BIF that processes/0 traps to.
 */

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

    /*
     * This bif cannot be called from erlang code. It can only be
     * trapped to from processes/0; therefore, a bad argument
     * is a processes/0 internal error.
     */

    ERTS_PROCS_DBG_TRACE(BIF_P->id, processes_trap, call);
    ERTS_PROCS_ASSERT(is_nil(BIF_ARG_1) || is_list(BIF_ARG_1));

    res_acc = BIF_ARG_1;

    ERTS_PROCS_ASSERT(ERTS_TERM_IS_MAGIC_BINARY(BIF_ARG_2));

    mbp = ((ProcBin *) binary_val(BIF_ARG_2))->val;

    ERTS_PROCS_ASSERT(ERTS_MAGIC_BIN_DESTRUCTOR(mbp)
		      == cleanup_processes_bif_data);
    ERTS_PROCS_ASSERT(
	((ErtsProcessesBifData *) ERTS_MAGIC_BIN_DATA(mbp))->debug.caller
	== BIF_P->id);

    if (processes_bif_engine(BIF_P, &res_acc, mbp)) {
	ERTS_PROCS_DBG_TRACE(BIF_P->id, processes_trap, return);
	BIF_RET(res_acc);
    }
    else {
	ERTS_PROCS_DBG_TRACE(BIF_P->id, processes_trap, trap);
	ERTS_BIF_YIELD2(&processes_trap_export, BIF_P, res_acc, BIF_ARG_2);
    }
}



/*
 * The actual processes/0 BIF.
 */

BIF_RETTYPE processes_0(BIF_ALIST_0)
{
    /*
     * A requirement: The list of pids returned should be a consistent
     *                snapshot of all processes existing at some point
     *                in time during the execution of processes/0. Since
     *                processes might terminate while processes/0 is
     *                executing, we have to keep track of terminated
     *                processes and add them to the result. We also
     *                ignore processes created after processes/0 has
     *                begun executing.
     */
    Eterm res_acc = NIL;
    Binary *mbp = erts_create_magic_binary(sizeof(ErtsProcessesBifData),
					   cleanup_processes_bif_data);
    ErtsProcessesBifData *pbdp = ERTS_MAGIC_BIN_DATA(mbp);

    ERTS_PROCS_DBG_TRACE(BIF_P->id, processes_0, call);
    pbdp->state = INITIALIZING;
    ERTS_PROCS_DBG_INIT(BIF_P, pbdp);

    if (ERTS_BIF_REDS_LEFT(BIF_P) >= ERTS_PROCESSES_BIF_MIN_START_REDS
	&& processes_bif_engine(BIF_P, &res_acc, mbp)) {
	erts_bin_free(mbp);
	ERTS_PROCS_DBG_CHK_RESLIST(res_acc);
	ERTS_PROCS_DBG_TRACE(BIF_P->id, processes_0, return);
	BIF_RET(res_acc);
    }
    else {
	Eterm *hp;
	Eterm magic_bin;
	ERTS_PROCS_DBG_CHK_RESLIST(res_acc);
	hp = HAlloc(BIF_P, PROC_BIN_SIZE);
	ERTS_PROCS_DBG_SAVE_HEAP_ALLOC(pbdp, hp, PROC_BIN_SIZE);
	magic_bin = erts_mk_magic_binary_term(&hp, &MSO(BIF_P), mbp);
	ERTS_PROCS_DBG_VERIFY_HEAP_ALLOC_USED(pbdp, hp);
	ERTS_PROCS_DBG_TRACE(BIF_P->id, processes_0, trap);
	ERTS_BIF_YIELD2(&processes_trap_export, BIF_P, res_acc, magic_bin);
    }
}

static void
init_processes_bif(void)
{
    saved_term_procs.start = NULL;
    saved_term_procs.end = NULL;
    processes_bif_tab_chunks = (((erts_max_processes - 1)
				 / ERTS_PROCESSES_BIF_TAB_CHUNK_SIZE)
				+ 1);

    /* processes_trap/2 is a hidden BIF that the processes/0 BIF traps to. */
    sys_memset((void *) &processes_trap_export, 0, sizeof(Export));
    processes_trap_export.address = &processes_trap_export.code[3];
    processes_trap_export.code[0] = am_erlang;
    processes_trap_export.code[1] = am_processes_trap;
    processes_trap_export.code[2] = 2;
    processes_trap_export.code[3] = (Eterm) em_apply_bif;
    processes_trap_export.code[4] = (Eterm) &processes_trap;

#if ERTS_PROCESSES_BIF_DEBUGLEVEL >= ERTS_PROCS_DBGLVL_CHK_TERM_PROC_LIST
    erts_get_emu_time(&debug_tv_start);
#endif

}

/*
 * Debug stuff
 */

Eterm
erts_debug_processes(Process *c_p)
{
    /* This is the old processes/0 BIF. */
    int i;
    Uint need;
    Eterm res;
    Eterm* hp;
    Process *p;
#ifdef DEBUG
    Eterm *hp_end;
#endif

    erts_smp_mtx_lock(&proc_tab_mtx);

    res = NIL;
    need = erts_process_count() * 2;
    hp = HAlloc(c_p, need); /* we need two heap words for each pid */
#ifdef DEBUG
    hp_end = hp + need;
#endif
     
    /* make the list by scanning bakward */


    for (i = erts_max_processes-1; i >= 0; i--) {
	if ((p = process_tab[i]) != NULL) {
	    res = CONS(hp, process_tab[i]->id, res);
	    hp += 2;
	}
    }
    ASSERT(hp == hp_end);

    erts_smp_mtx_unlock(&proc_tab_mtx);

    return res;
}

Eterm
erts_debug_processes_bif_info(Process *c_p)
{
    ERTS_DECL_AM(processes_bif_info);
    Eterm elements[] = {
	AM_processes_bif_info,
	make_small((Uint) ERTS_PROCESSES_BIF_MIN_START_REDS),
	make_small((Uint) processes_bif_tab_chunks),
	make_small((Uint) ERTS_PROCESSES_BIF_TAB_CHUNK_SIZE),
	make_small((Uint) ERTS_PROCESSES_BIF_TAB_INSPECT_INDICES_PER_RED),
	make_small((Uint) ERTS_PROCESSES_BIF_TAB_FREE_TERM_PROC_REDS),
	make_small((Uint) ERTS_PROCESSES_BIF_INSPECT_TERM_PROC_PER_RED),
	make_small((Uint) ERTS_PROCESSES_INSPECT_TERM_PROC_MAX_REDS),
	make_small((Uint) ERTS_PROCESSES_BIF_BUILD_RESULT_CONSES_PER_RED),
	make_small((Uint) ERTS_PROCESSES_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_PROCESSES_BIF_DEBUGLEVEL >= ERTS_PROCS_DBGLVL_CHK_FOUND_PIDS
static void
debug_processes_check_found_pid(ErtsProcessesBifData *pbdp,
				Eterm pid,
				SysTimeval *tvp,
				int pid_should_be_found)
{
    int i;
    for (i = 0; i < pbdp->pid_ix; i++) {
	if (pbdp->pid[i] == pid
	    && pbdp->debug.pid_started[i].tv_sec == tvp->tv_sec
	    && pbdp->debug.pid_started[i].tv_usec == tvp->tv_usec) {
	    ERTS_PROCS_ASSERT(pid_should_be_found);
	    return;
	}
    }
    ERTS_PROCS_ASSERT(!pid_should_be_found);
}
#endif

#if ERTS_PROCESSES_BIF_DEBUGLEVEL >= ERTS_PROCS_DBGLVL_CHK_RESLIST
static void
debug_processes_check_res_list(Eterm list)
{
    while (is_list(list)) {
	Eterm* consp = list_val(list);
	Eterm hd = CAR(consp);
	ERTS_PROCS_ASSERT(is_internal_pid(hd));
	list = CDR(consp);
    }

    ERTS_PROCS_ASSERT(is_nil(list));
}
#endif

#if ERTS_PROCESSES_BIF_DEBUGLEVEL >= ERTS_PROCS_DBGLVL_CHK_PIDS

static void
debug_processes_save_all_pids(ErtsProcessesBifData *pbdp)
{
    int ix, tix, cpix;
    pbdp->debug.correct_pids_verified = 0;
    pbdp->debug.correct_pids = erts_alloc(ERTS_ALC_T_PROCS_PIDS,
					  sizeof(Eterm)*pbdp->pid_sz);

    for (tix = 0, cpix = 0; tix < erts_max_processes; tix++) {
	Process *rp = process_tab[tix];
	if (rp) {
	    ERTS_PROCS_ASSERT(is_internal_pid(rp->id));
	    pbdp->debug.correct_pids[cpix++] = rp->id;
	    ERTS_PROCS_ASSERT(cpix <= pbdp->pid_sz);
	}
    }
    ERTS_PROCS_ASSERT(cpix == pbdp->pid_sz);

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

static void
debug_processes_verify_all_pids(ErtsProcessesBifData *pbdp)
{
    int ix, cpix;

    ERTS_PROCS_ASSERT(pbdp->pid_ix == pbdp->pid_sz);

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

    erts_free(ERTS_ALC_T_PROCS_PIDS, pbdp->debug.correct_pids);
    pbdp->debug.correct_pids = NULL;
}
#endif /* ERTS_PROCESSES_BIF_DEBUGLEVEL >= ERTS_PROCS_DBGLVL_CHK_PIDS */

#if ERTS_PROCESSES_BIF_DEBUGLEVEL >= ERTS_PROCS_DBGLVL_CHK_TERM_PROC_LIST
static void
debug_processes_check_term_proc_list(void)
{
    ERTS_SMP_LC_ASSERT(erts_lc_mtx_is_locked(&proc_tab_mtx));
    if (!saved_term_procs.start)
	ERTS_PROCS_ASSERT(!saved_term_procs.end);
    else {
	SysTimeval tv_now;
	SysTimeval *prev_xtvp = NULL;
	ErtsTermProcElement *tpep;
	erts_get_emu_time(&tv_now);

	for (tpep = saved_term_procs.start; tpep; tpep = tpep->next) {
	    if (!tpep->prev)
		ERTS_PROCS_ASSERT(saved_term_procs.start == tpep);
	    else
		ERTS_PROCS_ASSERT(tpep->prev->next == tpep);
	    if (!tpep->next)
		ERTS_PROCS_ASSERT(saved_term_procs.end == tpep);
	    else
		ERTS_PROCS_ASSERT(tpep->next->prev == tpep);
	    if (tpep->ix < 0) {
		SysTimeval *tvp = &tpep->u.bif_invocation.time;
		ERTS_PROCS_ASSERT(erts_cmp_timeval(&debug_tv_start, tvp) < 0
				  && erts_cmp_timeval(tvp, &tv_now) < 0);
	    }
	    else {
		SysTimeval *stvp = &tpep->u.process.spawned;
		SysTimeval *xtvp = &tpep->u.process.exited;
		
		ERTS_PROCS_ASSERT(erts_cmp_timeval(&debug_tv_start,
						   stvp) < 0);
		ERTS_PROCS_ASSERT(erts_cmp_timeval(stvp, xtvp) < 0);
		if (prev_xtvp)
		    ERTS_PROCS_ASSERT(erts_cmp_timeval(prev_xtvp, xtvp) < 0);
		prev_xtvp = xtvp;
		ERTS_PROCS_ASSERT(is_internal_pid(tpep->u.process.pid));
		ERTS_PROCS_ASSERT(tpep->ix
				  == internal_pid_index(tpep->u.process.pid));
	    }
	}
	
    }
}

static void
debug_processes_check_term_proc_free_list(ErtsTermProcElement *free_list)
{
    if (saved_term_procs.start) {
	ErtsTermProcElement *ftpep;
	ErtsTermProcElement *tpep;

	for (ftpep = free_list; ftpep; ftpep = ftpep->next) {
	    for (tpep = saved_term_procs.start; tpep; tpep = tpep->next)
		ERTS_PROCS_ASSERT(ftpep != tpep);
	}
    }
}

#endif

#if ERTS_PROCESSES_BIF_DEBUGLEVEL != 0

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

#endif

/*                                                                           *\
 * End of the processes/0 BIF implementation.                                *
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */