/*
    Copyright (c) 2016-2018 Chung, Hyung-Hwan. All rights reserved.

    Redistribution and use in source and binary forms, with or without
    modification, are permitted provided that the following conditions
    are met:
    1. Redistributions of source code must retain the above copyright
       notice, this list of conditions and the following disclaimer.
    2. Redistributions in binary form must reproduce the above copyright
       notice, this list of conditions and the following disclaimer in the
       documentation and/or other materials provided with the distribution.

    THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR
    IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
    OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
    IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
    NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
    DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
    THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "hak-prv.h"

#define ENABLE_SYSCMD

static const char* io_type_str[] =
{
	"input",
	"output"
};

static HAK_INLINE const char* proc_state_to_string (int state)
{
	static const hak_bch_t* str[] =
	{
		"TERMINATED",
		"SUSPENDED",
		"RUNNABLE",
		"WAITING",
		"RUNNING"
	};

	return str[state + 1];
}

static hak_ooch_t oocstr_colon[2] = { ':', '\0' };
static hak_ooch_t oocstr_dash[2] = { '-', '\0' };
static hak_ooch_t oocstr_none[1] = { '\0' };

#define PROC_MAP_INC 64

/* TODO: adjust these max semaphore pointer buffer capacity,
 *       proably depending on the object memory size? */
#define SEM_LIST_INC 256
#define SEM_HEAP_INC 256
#define SEM_IO_TUPLE_INC 256
#define SEM_LIST_MAX (SEM_LIST_INC * 1000)
#define SEM_HEAP_MAX (SEM_HEAP_INC * 1000)
#define SEM_IO_TUPLE_MAX (SEM_IO_TUPLE_INC * 1000)
#define SEM_IO_MAP_ALIGN 1024 /* this must a power of 2 */

#define SEM_HEAP_PARENT(x) (((x) - 1) / 2)
#define SEM_HEAP_LEFT(x)   ((x) * 2 + 1)
#define SEM_HEAP_RIGHT(x)  ((x) * 2 + 2)

#define SEM_HEAP_EARLIER_THAN(stx,x,y) ( \
	(HAK_OOP_TO_SMOOI((x)->u.timed.ftime_sec) < HAK_OOP_TO_SMOOI((y)->u.timed.ftime_sec)) || \
	(HAK_OOP_TO_SMOOI((x)->u.timed.ftime_sec) == HAK_OOP_TO_SMOOI((y)->u.timed.ftime_sec) && HAK_OOP_TO_SMOOI((x)->u.timed.ftime_nsec) < HAK_OOP_TO_SMOOI((y)->u.timed.ftime_nsec)) \
)

#define LOAD_IP(hak, v_ctx) ((hak)->ip = HAK_OOP_TO_SMOOI((v_ctx)->ip))
#define STORE_IP(hak, v_ctx) ((v_ctx)->ip = HAK_SMOOI_TO_OOP((hak)->ip))

#define LOAD_SP(hak, v_ctx) ((hak)->sp = HAK_OOP_TO_SMOOI((v_ctx)->sp))
#define STORE_SP(hak, v_ctx) ((v_ctx)->sp = HAK_SMOOI_TO_OOP((hak)->sp))

#define LOAD_ACTIVE_IP(hak) LOAD_IP(hak, (hak)->active_context)
#define STORE_ACTIVE_IP(hak) STORE_IP(hak, (hak)->active_context)

#define LOAD_ACTIVE_SP(hak) LOAD_SP(hak, (hak)->processor->active)
#define STORE_ACTIVE_SP(hak) STORE_SP(hak, (hak)->processor->active)

#define SWITCH_ACTIVE_CONTEXT(hak,v_ctx) \
	do \
	{ \
		STORE_ACTIVE_IP(hak); \
		(hak)->active_context = (v_ctx); \
		(hak)->active_function = (hak)->active_context->base; \
		(hak)->active_code = HAK_FUNCTION_GET_CODE_BYTE((hak)->active_function); \
		LOAD_ACTIVE_IP(hak); \
		(hak)->processor->active->current_context = (hak)->active_context; \
	} while (0)

/*#define FETCH_BYTE_CODE(hak) ((hak)->code.bc.arr->slot[(hak)->ip++])*/
#define FETCH_BYTE_CODE(hak) ((hak)->active_code[(hak)->ip++])
#define FETCH_BYTE_CODE_TO(hak, v_oow) (v_oow = FETCH_BYTE_CODE(hak))
#if (HAK_CODE_LONG_PARAM_SIZE == 2)
#	define FETCH_PARAM_CODE_TO(hak, v_oow) \
		do { \
			v_oow = FETCH_BYTE_CODE(hak); \
			v_oow = (v_oow << 8) | FETCH_BYTE_CODE(hak); \
		} while (0)
#else
#	define FETCH_PARAM_CODE_TO(hak, v_oow) (v_oow = FETCH_BYTE_CODE(hak))
#endif


#if defined(HAK_DEBUG_VM_EXEC)
#	define LOG_MASK_INST (HAK_LOG_IC | HAK_LOG_MNEMONIC | HAK_LOG_INFO)

#	define LOG_INST_0(hak,fmt) HAK_LOG1(hak, LOG_MASK_INST, "%010zd " fmt "\n", fetched_instruction_pointer)
#	define LOG_INST_1(hak,fmt,a1) HAK_LOG2(hak, LOG_MASK_INST, "%010zd " fmt "\n",fetched_instruction_pointer, a1)
#	define LOG_INST_2(hak,fmt,a1,a2) HAK_LOG3(hak, LOG_MASK_INST, "%010zd " fmt "\n", fetched_instruction_pointer, a1, a2)
#	define LOG_INST_3(hak,fmt,a1,a2,a3) HAK_LOG4(hak, LOG_MASK_INST, "%010zd " fmt "\n", fetched_instruction_pointer, a1, a2, a3)
#	define LOG_INST_4(hak,fmt,a1,a2,a3,a4) HAK_LOG5(hak, LOG_MASK_INST, "%010zd " fmt "\n", fetched_instruction_pointer, a1, a2, a3, a4)
#	define LOG_INST_5(hak,fmt,a1,a2,a3,a4,a5) HAK_LOG6(hak, LOG_MASK_INST, "%010zd " fmt "\n", fetched_instruction_pointer, a1, a2, a3, a4, a5)
#	define LOG_INST_6(hak,fmt,a1,a2,a3,a4,a5,a6) HAK_LOG7(hak, LOG_MASK_INST, "%010zd " fmt "\n", fetched_instruction_pointer, a1, a2, a3, a4, a5, a6)
#	define LOG_INST_7(hak,fmt,a1,a2,a3,a4,a5,a6,a7) HAK_LOG8(hak, LOG_MASK_INST, "%010zd " fmt "\n", fetched_instruction_pointer, a1, a2, a3, a4, a5, a6, a7)
#	define LOG_INST_8(hak,fmt,a1,a2,a3,a4,a5,a6,a7,a8) HAK_LOG9(hak, LOG_MASK_INST, "%010zd " fmt "\n", fetched_instruction_pointer, a1, a2, a3, a4, a5, a6, a7, a8)
#else
#	define LOG_INST_0(hak,fmt)
#	define LOG_INST_1(hak,fmt,a1)
#	define LOG_INST_2(hak,fmt,a1,a2)
#	define LOG_INST_3(hak,fmt,a1,a2,a3)
#	define LOG_INST_4(hak,fmt,a1,a2,a3,a4)
#	define LOG_INST_5(hak,fmt,a1,a2,a3,a4,a5)
#	define LOG_INST_6(hak,fmt,a1,a2,a3,a4,a5,a6)
#	define LOG_INST_7(hak,fmt,a1,a2,a3,a4,a5,a6,a7)
#	define LOG_INST_8(hak,fmt,a1,a2,a3,a4,a5,a6,a7,a8)
#endif

static int delete_sem_from_sem_io_tuple (hak_t* hak, hak_oop_semaphore_t sem, int force);
static void signal_io_semaphore (hak_t* hak, hak_ooi_t io_handle, hak_ooi_t mask);
static void terminate_all_processes (hak_t* hak);

/* ------------------------------------------------------------------------- */

#define HAK_EXSTACK_PUSH(hak, ctx_, ip_, clsp_, sp_) \
	do { \
		hak_oop_process_t ap = (hak)->processor->active; \
		hak_ooi_t exsp = HAK_OOP_TO_SMOOI(ap->exsp); \
		if (exsp >= HAK_OOP_TO_SMOOI(ap->exst) - 1) \
		{ \
			hak_seterrbfmt(hak, HAK_EOOMEM, "process exception stack overflow"); \
			(hak)->abort_req = -1; \
		} \
		exsp++; ap->slot[exsp] = (hak_oop_t)(ctx_); \
		exsp++; ap->slot[exsp] = HAK_SMOOI_TO_OOP(ip_); \
		exsp++; ap->slot[exsp] = HAK_SMOOI_TO_OOP(clsp_); \
		exsp++; ap->slot[exsp] = HAK_SMOOI_TO_OOP(sp_); \
		ap->exsp = HAK_SMOOI_TO_OOP(exsp); \
	} while (0)

#define HAK_EXSTACK_POP(hak) \
	do { \
		hak_oop_process_t ap = (hak)->processor->active; \
		hak_ooi_t exsp = HAK_OOP_TO_SMOOI(ap->exsp); \
		exsp -= 4; \
		ap->exsp = HAK_SMOOI_TO_OOP(exsp); \
	} while (0)

#define HAK_EXSTACK_POP_TO(hak, ctx_, ip_, clsp_, sp_) \
	do { \
		hak_oop_process_t ap = (hak)->processor->active; \
		hak_ooi_t exsp = HAK_OOP_TO_SMOOI(ap->exsp); \
		sp_ = HAK_OOP_TO_SMOOI(ap->slot[exsp]); exsp--; \
		clsp_ = HAK_OOP_TO_SMOOI(ap->slot[exsp]); exsp--; \
		ip_ = HAK_OOP_TO_SMOOI(ap->slot[exsp]); exsp--; \
		ctx_ = (hak_oop_context_t)ap->slot[exsp]; exsp--; \
		ap->exsp = HAK_SMOOI_TO_OOP(exsp); \
	} while (0)

#define HAK_EXSTACK_GET_ST(hak) HAK_OOP_TO_SMOOI(((hak)->processor->active)->exst)
#define HAK_EXSTACK_GET_SP(hak) HAK_OOP_TO_SMOOI(((hak)->processor->active)->exsp)

#define HAK_EXSTACK_IS_EMPTY(hak) (HAK_OOP_TO_SMOOI(((hak)->processor->active)->exsp) <= HAK_OOP_TO_SMOOI(((hak)->processor->active)->st))


/* ------------------------------------------------------------------------- */

#define HAK_CLSTACK_PUSH(hak, v) \
	do { \
		hak_oop_process_t ap = (hak)->processor->active; \
		hak_ooi_t clsp_ = HAK_OOP_TO_SMOOI(ap->clsp); \
		if (clsp_ >= HAK_OOP_TO_SMOOI(ap->clst)) \
		{ \
			hak_seterrbfmt(hak, HAK_EOOMEM, "process class stack overflow"); \
			(hak)->abort_req = -1; \
		} \
		clsp_++; ap->slot[clsp_] = (v); \
		ap->clsp = HAK_SMOOI_TO_OOP(clsp_); \
	} while (0)

#define HAK_CLSTACK_POP(hak) \
	do { \
		hak_oop_process_t ap = (hak)->processor->active; \
		hak_ooi_t clsp_ = HAK_OOP_TO_SMOOI(ap->clsp); \
		clsp_--; \
		ap->clsp = HAK_SMOOI_TO_OOP(clsp_); \
	} while (0)

#define HAK_CLSTACK_POPS(hak, count) \
	do { \
		hak_oop_process_t ap = (hak)->processor->active; \
		hak_ooi_t clsp_ = HAK_OOP_TO_SMOOI(ap->clsp); \
		clsp_ -= count; \
		ap->clsp = HAK_SMOOI_TO_OOP(clsp_); \
	} while (0)

#define HAK_CLSTACK_POP_TO(hak, v) \
	do { \
		hak_oop_process_t ap = (hak)->processor->active; \
		hak_ooi_t clsp_ = HAK_OOP_TO_SMOOI(ap->clsp); \
		v = ap->slot[clsp_]; clsp_--; \
		ap->clsp = HAK_SMOOI_TO_OOP(clsp_); \
	} while (0)

#define HAK_CLSTACK_FETCH_TOP_TO(hak, v) \
	do { \
		hak_oop_process_t ap = (hak)->processor->active; \
		hak_ooi_t clsp_ = HAK_OOP_TO_SMOOI(ap->clsp); \
		v = ap->slot[clsp_]; \
	} while (0)


#define HAK_CLSTACK_CHOP(hak, clsp_) ((hak)->processor->active->clsp = HAK_SMOOI_TO_OOP(clsp_))

#define HAK_CLSTACK_GET_ST(hak) HAK_OOP_TO_SMOOI(((hak)->processor->active)->clst)
#define HAK_CLSTACK_GET_SP(hak) HAK_OOP_TO_SMOOI(((hak)->processor->active)->clsp)

#define HAK_CLSTACK_IS_EMPTY(hak) (HAK_OOP_TO_SMOOI(((hak)->processor->active)->clsp) <= HAK_OOP_TO_SMOOI(((hak)->processor->active)->exst))

/* ------------------------------------------------------------------------- */

static HAK_INLINE int vm_startup (hak_t* hak)
{
	hak_cb_t* cb;
	hak_oow_t i;

	HAK_DEBUG1 (hak, "VM started up at IP %zd\n", hak->ip);

	for (cb = hak->cblist; cb; cb = cb->next)
	{
		if (cb->vm_startup && cb->vm_startup(hak) <= -1)
		{
			for (cb = cb->prev; cb; cb = cb->prev)
			{
				if (cb->vm_cleanup) cb->vm_cleanup(hak);
			}
			return -1;
		}
	}

	for (i = 0; i < hak->sem_io_map_capa; i++)
	{
		hak->sem_io_map[i] = -1;
	}

#if defined(ENABLE_GCFIN)
	hak->sem_gcfin = (hak_oop_semaphore_t)hak->_nil;
	hak->sem_gcfin_sigreq = 0;
#endif

	hak->vmprim.vm_gettime(hak, &hak->exec_start_time); /* raw time. no adjustment */

	return 0;
}

static void vm_cleanup (hak_t* hak)
{
	hak_cb_t* cb;
	hak_oow_t i;

	if (hak->processor->total_count != HAK_SMOOI_TO_OOP(0))
	{
		/* if there is a suspended process, your program is probably wrong */
		HAK_LOG3(hak, HAK_LOG_WARN, "Warning - non-zero number of processes upon VM clean-up - total: %zd runnable: %zd suspended: %zd\n",
			(hak_ooi_t)HAK_OOP_TO_SMOOI(hak->processor->total_count),
			(hak_ooi_t)HAK_OOP_TO_SMOOI(hak->processor->runnable.count),
			(hak_ooi_t)HAK_OOP_TO_SMOOI(hak->processor->suspended.count));

		HAK_LOG0 (hak, HAK_LOG_WARN, "Warning - terminating all residue processes\n");
		terminate_all_processes(hak);
	}

	HAK_ASSERT(hak, hak->processor->active == hak->nil_process);
	HAK_ASSERT(hak, HAK_OOP_TO_SMOOI(hak->processor->total_count) == 0);
	HAK_ASSERT(hak, HAK_OOP_TO_SMOOI(hak->processor->runnable.count) == 0);
	HAK_ASSERT(hak, HAK_OOP_TO_SMOOI(hak->processor->suspended.count) == 0);

	for (i = 0; i < hak->sem_io_map_capa;)
	{
		hak_ooi_t sem_io_index;
		if ((sem_io_index = hak->sem_io_map[i]) >= 0)
		{
			HAK_ASSERT(hak, sem_io_index < hak->sem_io_tuple_count);
			HAK_ASSERT(hak, hak->sem_io_tuple[sem_io_index].sem[HAK_SEMAPHORE_IO_TYPE_INPUT] ||
			                 hak->sem_io_tuple[sem_io_index].sem[HAK_SEMAPHORE_IO_TYPE_OUTPUT]);

			if (hak->sem_io_tuple[sem_io_index].sem[HAK_SEMAPHORE_IO_TYPE_INPUT])
			{
				delete_sem_from_sem_io_tuple(hak, hak->sem_io_tuple[sem_io_index].sem[HAK_SEMAPHORE_IO_TYPE_INPUT], 1);
			}
			if (hak->sem_io_tuple[sem_io_index].sem[HAK_SEMAPHORE_IO_TYPE_OUTPUT])
			{
				delete_sem_from_sem_io_tuple(hak, hak->sem_io_tuple[sem_io_index].sem[HAK_SEMAPHORE_IO_TYPE_OUTPUT], 1);
			}

			HAK_ASSERT(hak, hak->sem_io_map[i] <= -1);
		}
		else
		{
			i++;
		}
	}

	HAK_ASSERT(hak, hak->sem_io_tuple_count == 0);
	HAK_ASSERT(hak, hak->sem_io_count == 0);

	hak->vmprim.vm_gettime(hak, &hak->exec_end_time); /* raw time. no adjustment */
	for (cb = hak->cblist; cb; cb = cb->next)
	{
		if (cb->vm_cleanup) cb->vm_cleanup(hak);
	}

#if defined(ENABLE_GCFIN)
	hak->sem_gcfin = (hak_oop_semaphore_t)hak->_nil;
	hak->sem_gcfin_sigreq = 0;

	/* deregister all pending finalizable objects pending just in case these
	 * have not been removed for various reasons. (e.g. sudden VM abortion)
	 */
	hak_deregallfinalizables(hak);
#endif

	HAK_DEBUG0 (hak, "VM cleaned up\n");
}

static HAK_INLINE void vm_gettime (hak_t* hak, hak_ntime_t* now)
{
	hak->vmprim.vm_gettime(hak, now);
	/* in vm_startup(), hak->exec_start_time has been set to the time of
	 * that moment. time returned here get offset by hak->exec_start_time and
	 * thus becomes relative to it. this way, it is kept small such that it
	 * can be represented in a small integer with leaving almost zero chance
	 * of overflow. */
	HAK_SUB_NTIME (now, now, &hak->exec_start_time);  /* now = now - exec_start_time */
}

static HAK_INLINE int vm_sleep (hak_t* hak, const hak_ntime_t* dur)
{
/* TODO: return 1 if it gets into the halting state */
	hak->vmprim.vm_sleep(hak, dur);
	return 0;
}

static HAK_INLINE void vm_muxwait (hak_t* hak, const hak_ntime_t* dur)
{
	hak->vmprim.vm_muxwait(hak, dur, signal_io_semaphore);
}

static void vm_checkbc (hak_t* hak, hak_oob_t bcode)
{
	hak_cb_t* cb;
	for (cb = hak->cblist; cb; cb = cb->next)
	{
		if (cb->vm_checkbc) cb->vm_checkbc(hak, bcode);
	}
}

/* ------------------------------------------------------------------------- */

static HAK_INLINE hak_oop_context_t make_context (hak_t* hak, hak_ooi_t ntmprs)
{
	hak_oop_context_t ctx;

	HAK_ASSERT(hak, ntmprs >= 0);
	/*return (hak_oop_context_t)hak_allocoopobj(hak, HAK_BRAND_CONTEXT, HAK_CONTEXT_NAMED_INSTVARS + (hak_oow_t)ntmprs);*/
	ctx = (hak_oop_context_t)hak_instantiate(hak, hak->c_block_context, HAK_NULL, ntmprs);

	/* TODO: a good way to initialize smooi field to 0 in hak_instantiate()?
	 *      for this, there must be a way to specify the type of the member variables...
	 *      it's error-prone to initialize the numeric value to nil where 0 is necessary */

	if (HAK_LIKELY(ctx)) ctx->ivaroff = HAK_SMOOI_TO_OOP(0);
	return ctx;
}

static HAK_INLINE hak_oop_function_t make_function (hak_t* hak, hak_oow_t lfsize, const hak_oob_t* bptr, hak_oow_t blen, hak_dbgi_t* dbgi)
{
	hak_oop_function_t func;

	/* the literal frame is placed in the variable part.
	 * the byte code is placed in the trailer space. */
	/*func = (hak_oop_function_t)hak_allocoopobjwithtrailer(hak, HAK_BRAND_FUNCTION, HAK_FUNCTION_NAMED_INSTVARS + lfsize, bptr, blen);*/
	func = (hak_oop_function_t)hak_instantiatewithtrailer(hak, hak->c_function, lfsize, bptr, blen);
	if (HAK_UNLIKELY(!func)) return HAK_NULL;

	if (dbgi)
	{
		hak_oop_t tmp;
		hak_pushvolat(hak, (hak_oop_t*)&func);
		tmp = hak_makebytearray(hak, (hak_oob_t*)dbgi, HAK_SIZEOF(*dbgi) * blen);
		hak_popvolat(hak);
		if (HAK_LIKELY(tmp)) func->dbgi = tmp;
	}

	func->attr_mask = HAK_SMOOI_TO_OOP(0);
	return func;
}

static HAK_INLINE void fill_function_data (hak_t* hak, hak_oop_function_t func, hak_ooi_t attr_mask, hak_oow_t name_lfindex, hak_oop_context_t homectx, const hak_oop_t* lfptr, hak_oow_t lfsize)
{
	/* Although this function could be integrated into make_function(),
	 * this function has been separated from make_function() to make GC handling simpler */
	hak_oow_t i;

	HAK_ASSERT(hak, attr_mask >= 0 && attr_mask <= HAK_SMOOI_MAX);

	/* copy literal frames */
	HAK_ASSERT(hak, lfsize <= HAK_OBJ_GET_SIZE(func) - HAK_FUNCTION_NAMED_INSTVARS);
	for (i = 0; i < lfsize; i++)
	{
		func->literal_frame[i] = lfptr[i];
	#if 0
		HAK_DEBUG2 (hak, "literal frame %d => %O\n", (int)i, lfptr[i]);
	#endif
	}

	/* initialize other fields */
	if (name_lfindex < lfsize)
	{
		/* the compiler must ensure that lfsize is less than the maximum extended long parameter value */
		func->name = hak->active_function->literal_frame[name_lfindex];
	}

	func->home = homectx;
	func->attr_mask = HAK_SMOOI_TO_OOP(attr_mask);
	func->literal_frame_size = HAK_SMOOI_TO_OOP(lfsize);
}

static HAK_INLINE hak_oop_block_t make_compiled_block (hak_t* hak)
{
	/* create a base block used for creation of a block context */
	/*return (hak_oop_block_t)hak_allocoopobj(hak, HAK_BRAND_BLOCK, HAK_BLOCK_NAMED_INSTVARS);*/
	return (hak_oop_block_t)hak_instantiate(hak, hak->c_compiled_block, HAK_NULL, 0);
}

static HAK_INLINE void fill_block_data (hak_t* hak, hak_oop_block_t blk, hak_ooi_t attr_mask, hak_oow_t name_lfindex, hak_ooi_t ip, hak_oop_context_t homectx)
{
	HAK_ASSERT(hak, attr_mask >= 0 && attr_mask <= HAK_SMOOI_MAX);
	HAK_ASSERT(hak, ip >= 0 && ip <= HAK_SMOOI_MAX);

	if (name_lfindex < HAK_OOP_TO_SMOOI(hak->active_function->literal_frame_size))
		blk->name = hak->active_function->literal_frame[name_lfindex];

	blk->home = homectx;
	blk->ip = HAK_SMOOI_TO_OOP(ip);
	blk->attr_mask = HAK_SMOOI_TO_OOP(attr_mask);
}

static HAK_INLINE int prepare_to_alloc_pid (hak_t* hak)
{
	hak_oow_t new_capa;
	hak_ooi_t i, j;
	hak_oop_t* tmp;

	HAK_ASSERT(hak, hak->proc_map_free_first <= -1);
	HAK_ASSERT(hak, hak->proc_map_free_last <= -1);

	new_capa = hak->proc_map_capa + PROC_MAP_INC;
	if (new_capa > HAK_SMOOI_MAX)
	{
		if (hak->proc_map_capa >= HAK_SMOOI_MAX)
		{
		#if defined(HAK_DEBUG_VM_PROCESSOR)
			HAK_LOG0 (hak, HAK_LOG_IC | HAK_LOG_FATAL, "Processor - too many processes\n");
		#endif
			hak_seterrbfmt(hak, HAK_EPFULL, "maximum number(%zd) of processes reached", HAK_SMOOI_MAX);
			return -1;
		}

		new_capa = HAK_SMOOI_MAX;
	}

	tmp = (hak_oop_t*)hak_reallocmem(hak, hak->proc_map, HAK_SIZEOF(hak_oop_t) * new_capa);
	if (HAK_UNLIKELY(!tmp)) return -1;

	hak->proc_map_free_first = hak->proc_map_capa;
	for (i = hak->proc_map_capa, j = hak->proc_map_capa + 1; j < new_capa; i++, j++)
	{
		tmp[i] = HAK_SMOOI_TO_OOP(j);
	}
	tmp[i] = HAK_SMOOI_TO_OOP(-1);
	hak->proc_map_free_last = i;

	hak->proc_map = tmp;
	hak->proc_map_capa = new_capa;

	return 0;
}

static HAK_INLINE void alloc_pid (hak_t* hak, hak_oop_process_t proc)
{
	hak_ooi_t pid;

	pid = hak->proc_map_free_first;
	proc->id = HAK_SMOOI_TO_OOP(pid);
	HAK_ASSERT(hak, HAK_OOP_IS_SMOOI(hak->proc_map[pid]));
	hak->proc_map_free_first = HAK_OOP_TO_SMOOI(hak->proc_map[pid]);
	if (hak->proc_map_free_first <= -1) hak->proc_map_free_last = -1;
	hak->proc_map[pid] = (hak_oop_t)proc;
	hak->proc_map_used++;
}

static HAK_INLINE void free_pid (hak_t* hak, hak_oop_process_t proc)
{
	hak_ooi_t pid;

	pid = HAK_OOP_TO_SMOOI(proc->id);
	HAK_ASSERT(hak, pid < hak->proc_map_capa);
	HAK_ASSERT(hak, hak->proc_map_used > 0);

	hak->proc_map[pid] = HAK_SMOOI_TO_OOP(-1);
	if (hak->proc_map_free_last <= -1)
	{
		HAK_ASSERT(hak, hak->proc_map_free_first <= -1);
		hak->proc_map_free_first = pid;
	}
	else
	{
		hak->proc_map[hak->proc_map_free_last] = HAK_SMOOI_TO_OOP(pid);
	}
	hak->proc_map_free_last = pid;
	hak->proc_map_used--;
}


static hak_oop_process_t make_process (hak_t* hak, hak_oop_context_t c)
{
	hak_oop_process_t proc;
	hak_oow_t stksize, exstksize, clstksize, maxsize;
	hak_ooi_t total_count;
	hak_ooi_t suspended_count;

	total_count = HAK_OOP_TO_SMOOI(hak->processor->total_count);
	if (total_count >= HAK_SMOOI_MAX)
	{
	#if defined(HAK_DEBUG_VM_PROCESSOR)
		HAK_LOG0 (hak, HAK_LOG_IC | HAK_LOG_FATAL, "Processor - too many processes\n");
	#endif
		hak_seterrbfmt(hak, HAK_EPFULL, "maximum number(%zd) of processes reached", HAK_SMOOI_MAX);
		return HAK_NULL;
	}

	if (hak->proc_map_free_first <= -1 && prepare_to_alloc_pid(hak) <= -1) return HAK_NULL;

	stksize = hak->option.dfl_procstk_size; /* stack */
	exstksize = 128; /* exception stack size */ /* TODO: make it configurable */
	clstksize = 64; /* class stack size */ /* TODO: make it configurable too */

	maxsize = (HAK_TYPE_MAX(hak_ooi_t) - HAK_PROCESS_NAMED_INSTVARS) / 3;

	if (stksize > maxsize) stksize = maxsize;
	else if (stksize < 192) stksize = 192;

	if (exstksize > maxsize) exstksize = maxsize;
	else if (exstksize < 128) exstksize = 128;

	if (clstksize > maxsize) clstksize = maxsize;
	else if (clstksize < 32) clstksize = 32;

	hak_pushvolat(hak, (hak_oop_t*)&c);
	proc = (hak_oop_process_t)hak_instantiate(hak, hak->c_process, HAK_NULL, stksize + exstksize + clstksize);
	hak_popvolat(hak);
	if (HAK_UNLIKELY(!proc))
	{
		const hak_ooch_t* oldmsg = hak_backuperrmsg(hak);
		hak_seterrbfmt(hak, hak->errnum,
			"unable to instantiate %O - %js", hak->c_process->name, oldmsg);
		return HAK_NULL;
	}

	HAK_OBJ_SET_FLAGS_PROC (proc, 1); /* a special flag to indicate an object is a process instance */
	proc->state = HAK_SMOOI_TO_OOP(HAK_PROCESS_STATE_SUSPENDED);

	/* assign a process id to the process */
	alloc_pid(hak, proc);

	proc->initial_context = c;
	proc->current_context = c;

	/* stack */
	proc->sp = HAK_SMOOI_TO_OOP(-1); /* no item */
	proc->st = HAK_SMOOI_TO_OOP(stksize - 1);

	/* exception stack */
	proc->exsp = proc->st; /* no item pushed yet */
	proc->exst = HAK_SMOOI_TO_OOP(stksize + exstksize - 1);

	/* class stack */
	proc->clsp = proc->exst; /* no item pushed yet */
	proc->clst = HAK_SMOOI_TO_OOP(stksize + exstksize + clstksize - 1);

	HAK_ASSERT(hak, (hak_oop_t)c->sender == hak->_nil);

#if defined(HAK_DEBUG_VM_PROCESSOR)
	HAK_LOG2(hak, HAK_LOG_IC | HAK_LOG_DEBUG, "Processor - process[%zd] **CREATED**->%hs\n", HAK_OOP_TO_SMOOI(proc->id), proc_state_to_string(HAK_OOP_TO_SMOOI(proc->state)));
#endif

	/* a process is created in the SUSPENDED state. chain it to the suspended process list */
	suspended_count = HAK_OOP_TO_SMOOI(hak->processor->suspended.count);
	HAK_APPEND_TO_OOP_LIST(hak, &hak->processor->suspended, hak_oop_process_t, proc, ps);
	suspended_count++;
	hak->processor->suspended.count = HAK_SMOOI_TO_OOP(suspended_count);

	total_count++;
	hak->processor->total_count = HAK_SMOOI_TO_OOP(total_count);

	return proc;
}

static HAK_INLINE void sleep_active_process (hak_t* hak, int state)
{
	STORE_ACTIVE_SP(hak);

	/* store the current active context to the current process.
	 * it is the suspended context of the process to be suspended */
	HAK_ASSERT(hak, hak->processor->active != hak->nil_process);

#if defined(HAK_DEBUG_VM_PROCESSOR)
	HAK_LOG3(hak, HAK_LOG_IC | HAK_LOG_DEBUG, "Processor - process[%zd] %hs->%hs in sleep_active_process\n", HAK_OOP_TO_SMOOI(hak->processor->active->id), proc_state_to_string(HAK_OOP_TO_SMOOI(hak->processor->active->state)), proc_state_to_string(state));
#endif

	hak->processor->active->current_context = hak->active_context;
	hak->processor->active->state = HAK_SMOOI_TO_OOP(state);
}

static HAK_INLINE void wake_process (hak_t* hak, hak_oop_process_t proc)
{
#if defined(HAK_DEBUG_VM_PROCESSOR)
	HAK_LOG2(hak, HAK_LOG_IC | HAK_LOG_DEBUG, "Processor - process[%zd] %hs->RUNNING in wake_process\n", HAK_OOP_TO_SMOOI(proc->id), proc_state_to_string(HAK_OOP_TO_SMOOI(proc->state)));
#endif

	/* activate the given process */
	HAK_ASSERT(hak, proc->state == HAK_SMOOI_TO_OOP(HAK_PROCESS_STATE_RUNNABLE));
	proc->state = HAK_SMOOI_TO_OOP(HAK_PROCESS_STATE_RUNNING);
	hak->processor->active = proc;

	LOAD_ACTIVE_SP(hak);

	/* activate the suspended context of the new process */
	SWITCH_ACTIVE_CONTEXT(hak, proc->current_context);

#if defined(HAK_DEBUG_VM_PROCESSOR) && (HAK_DEBUG_VM_PROCESSOR >= 2)
	HAK_LOG3(hak, HAK_LOG_IC | HAK_LOG_DEBUG, "Processor - woke up process[%zd] context %O ip=%zd\n", HAK_OOP_TO_SMOOI(hak->processor->active->id), hak->active_context, hak->ip);
#endif
}

static void switch_to_process (hak_t* hak, hak_oop_process_t proc, int new_state_for_old_active)
{
	/* the new process must not be the currently active process */
	HAK_ASSERT(hak, hak->processor->active != proc);

	/* the new process must be in the runnable state */
	HAK_ASSERT(hak, proc->state == HAK_SMOOI_TO_OOP(HAK_PROCESS_STATE_RUNNABLE) ||
	                 proc->state == HAK_SMOOI_TO_OOP(HAK_PROCESS_STATE_WAITING));

	sleep_active_process(hak, new_state_for_old_active);
	wake_process(hak, proc);

	hak->proc_switched = 1;
}

static HAK_INLINE void switch_to_process_from_nil (hak_t* hak, hak_oop_process_t proc)
{
	HAK_ASSERT(hak, hak->processor->active == hak->nil_process);
	wake_process(hak, proc);
	hak->proc_switched = 1;
}

static HAK_INLINE hak_oop_process_t find_next_runnable_process (hak_t* hak)
{
	hak_oop_process_t nrp;
	HAK_ASSERT(hak, hak->processor->active->state == HAK_SMOOI_TO_OOP(HAK_PROCESS_STATE_RUNNING));
	nrp = hak->processor->active->ps.next;
	if ((hak_oop_t)nrp == hak->_nil) nrp = hak->processor->runnable.first;
	return nrp;
}

static HAK_INLINE void switch_to_next_runnable_process (hak_t* hak)
{
	hak_oop_process_t nrp;
	nrp = find_next_runnable_process(hak);
	if (nrp != hak->processor->active) switch_to_process(hak, nrp, HAK_PROCESS_STATE_RUNNABLE);
}

static HAK_INLINE void chain_into_processor (hak_t* hak, hak_oop_process_t proc, int new_state)
{
	/* the process is not scheduled at all.
	 * link it to the processor's process list. */
	hak_ooi_t runnable_count;
	hak_ooi_t suspended_count;

	/*HAK_ASSERT(hak, (hak_oop_t)proc->ps.prev == hak->_nil);
	HAK_ASSERT(hak, (hak_oop_t)proc->ps.next == hak->_nil);*/

	HAK_ASSERT(hak, proc->state == HAK_SMOOI_TO_OOP(HAK_PROCESS_STATE_SUSPENDED));
	HAK_ASSERT(hak, new_state == HAK_PROCESS_STATE_RUNNABLE || new_state == HAK_PROCESS_STATE_RUNNING);

#if defined(HAK_DEBUG_VM_PROCESSOR)
	HAK_LOG3(hak, HAK_LOG_IC | HAK_LOG_DEBUG,
		"Processor - process[%zd] %hs->%hs in chain_into_processor\n",
		HAK_OOP_TO_SMOOI(proc->id),
		proc_state_to_string(HAK_OOP_TO_SMOOI(proc->state)),
		proc_state_to_string(new_state));
#endif

	runnable_count = HAK_OOP_TO_SMOOI(hak->processor->runnable.count);

	HAK_ASSERT(hak, runnable_count >= 0);

	suspended_count = HAK_OOP_TO_SMOOI(hak->processor->suspended.count);
	HAK_DELETE_FROM_OOP_LIST(hak, &hak->processor->suspended, proc, ps);
	suspended_count--;
	hak->processor->suspended.count = HAK_SMOOI_TO_OOP(suspended_count);

	/* append to the runnable list */
	HAK_APPEND_TO_OOP_LIST(hak, &hak->processor->runnable, hak_oop_process_t, proc, ps);
	proc->state = HAK_SMOOI_TO_OOP(new_state);

	runnable_count++;
	hak->processor->runnable.count = HAK_SMOOI_TO_OOP(runnable_count);
}

static HAK_INLINE void unchain_from_processor (hak_t* hak, hak_oop_process_t proc, int new_state)
{
	hak_ooi_t runnable_count;
	hak_ooi_t suspended_count;
	hak_ooi_t total_count;

	HAK_ASSERT(hak, proc->state == HAK_SMOOI_TO_OOP(HAK_PROCESS_STATE_RUNNING) ||
	                proc->state == HAK_SMOOI_TO_OOP(HAK_PROCESS_STATE_RUNNABLE) ||
	                proc->state == HAK_SMOOI_TO_OOP(HAK_PROCESS_STATE_SUSPENDED));

	HAK_ASSERT(hak, proc->state != HAK_SMOOI_TO_OOP(new_state));

#if defined(HAK_DEBUG_VM_PROCESSOR)
	HAK_LOG3(hak, HAK_LOG_IC | HAK_LOG_DEBUG, "Processor - process[%zd] %hs->%hs in unchain_from_processor\n", HAK_OOP_TO_SMOOI(proc->id), proc_state_to_string(HAK_OOP_TO_SMOOI(proc->state)), proc_state_to_string(HAK_OOP_TO_SMOOI(new_state)));
#endif

	if (proc->state == HAK_SMOOI_TO_OOP(HAK_PROCESS_STATE_SUSPENDED))
	{
		suspended_count = HAK_OOP_TO_SMOOI(hak->processor->suspended.count);
		HAK_ASSERT(hak, suspended_count > 0);
		HAK_DELETE_FROM_OOP_LIST(hak, &hak->processor->suspended, proc, ps);
		suspended_count--;
		hak->processor->suspended.count = HAK_SMOOI_TO_OOP(suspended_count);
	}
	else
	{
		runnable_count = HAK_OOP_TO_SMOOI(hak->processor->runnable.count);
		HAK_ASSERT(hak, runnable_count > 0);
		HAK_DELETE_FROM_OOP_LIST(hak, &hak->processor->runnable, proc, ps);
		runnable_count--;
		hak->processor->runnable.count = HAK_SMOOI_TO_OOP(runnable_count);
		if (runnable_count == 0) hak->processor->active = hak->nil_process;
	}

	if (new_state == HAK_PROCESS_STATE_TERMINATED)
	{
		/* do not chain it to the suspended process list as it's being terminated */
		proc->ps.prev = (hak_oop_process_t)hak->_nil;
		proc->ps.next = (hak_oop_process_t)hak->_nil;

		total_count = HAK_OOP_TO_SMOOI(hak->processor->total_count);
		total_count--;
		hak->processor->total_count = HAK_SMOOI_TO_OOP(total_count);
	}
	else
	{
		/* append to the suspended process list */
		HAK_ASSERT(hak, new_state == HAK_PROCESS_STATE_SUSPENDED);

		suspended_count = HAK_OOP_TO_SMOOI(hak->processor->suspended.count);
		HAK_APPEND_TO_OOP_LIST(hak, &hak->processor->suspended, hak_oop_process_t, proc, ps);
		suspended_count++;
		hak->processor->suspended.count= HAK_SMOOI_TO_OOP(suspended_count);
	}

	proc->state = HAK_SMOOI_TO_OOP(new_state);
}

static HAK_INLINE void chain_into_semaphore (hak_t* hak, hak_oop_process_t proc, hak_oop_semaphore_t sem)
{
	/* append a process to the process list of a semaphore or a semaphore group */

	/* a process chained to a semaphore cannot get chained to
	 * a semaphore again. a process can get chained to a single semaphore
	 * or a single semaphore group only */
	if ((hak_oop_t)proc->sem != hak->_nil) return; /* ignore it if it happens anyway. TODO: is it desirable???? */

	HAK_ASSERT(hak, (hak_oop_t)proc->sem == hak->_nil);
	HAK_ASSERT(hak, (hak_oop_t)proc->sem_wait.prev == hak->_nil);
	HAK_ASSERT(hak, (hak_oop_t)proc->sem_wait.next == hak->_nil);

	/* a semaphore or a semaphore group must be given for process chaining */
	HAK_ASSERT(hak, HAK_IS_SEMAPHORE(hak, sem) || HAK_IS_SEMAPHORE_GROUP(hak, sem));

	/* i assume the head part of the semaphore has the same layout as
	 * the semaphore group */
	HAK_ASSERT(hak, HAK_OFFSETOF(hak_semaphore_t,waiting) ==
	                 HAK_OFFSETOF(hak_semaphore_group_t,waiting));

	HAK_APPEND_TO_OOP_LIST(hak, &sem->waiting, hak_oop_process_t, proc, sem_wait);

	proc->sem = (hak_oop_t)sem;
}

static HAK_INLINE void unchain_from_semaphore (hak_t* hak, hak_oop_process_t proc)
{
	hak_oop_semaphore_t sem;

	HAK_ASSERT(hak, (hak_oop_t)proc->sem != hak->_nil);
	HAK_ASSERT(hak, HAK_IS_SEMAPHORE(hak, proc->sem) || HAK_IS_SEMAPHORE_GROUP(hak, proc->sem));
	HAK_ASSERT(hak, HAK_OFFSETOF(hak_semaphore_t,waiting) == HAK_OFFSETOF(hak_semaphore_group_t,waiting));

	/* proc->sem may be one of a semaphore or a semaphore group.
	 * i assume that 'waiting' is defined to the same position
	 * in both Semaphore and SemaphoreGroup. there is no need to
	 * write different code for each class. */
	sem = (hak_oop_semaphore_t)proc->sem;  /* semgrp = (hak_oop_semaphore_group_t)proc->sem */
	HAK_DELETE_FROM_OOP_LIST(hak, &sem->waiting, proc, sem_wait);

	proc->sem_wait.prev = (hak_oop_process_t)hak->_nil;
	proc->sem_wait.next = (hak_oop_process_t)hak->_nil;
	proc->sem = hak->_nil;
}

static void dump_process_info (hak_t* hak, hak_bitmask_t log_mask)
{
	if (HAK_OOP_TO_SMOOI(hak->processor->runnable.count) > 0)
	{
		hak_oop_process_t p;
		HAK_LOG0 (hak, log_mask, "> Runnable:");
		p = hak->processor->runnable.first;
		while (p)
		{
			HAK_LOG1(hak, log_mask, " %O", p->id);
			if (p == hak->processor->runnable.last) break;
			p = p->ps.next;
		}
		HAK_LOG0 (hak, log_mask, "\n");
	}
	if (HAK_OOP_TO_SMOOI(hak->processor->suspended.count) > 0)
	{
		hak_oop_process_t p;
		HAK_LOG0 (hak, log_mask, "> Suspended:");
		p = hak->processor->suspended.first;
		while (p)
		{
			HAK_LOG1(hak, log_mask, " %O", p->id);
			if (p == hak->processor->suspended.last) break;
			p = p->ps.next;
		}
		HAK_LOG0 (hak, log_mask, "\n");
	}
	if (hak->sem_io_wait_count > 0)
	{
		hak_ooi_t io_handle;

		HAK_LOG0 (hak, log_mask, "> IO semaphores:");
		for (io_handle = 0; io_handle < hak->sem_io_map_capa; io_handle++)
		{
			hak_ooi_t index;

			index = hak->sem_io_map[io_handle];
			if (index >= 0)
			{
				hak_oop_semaphore_t sem;

				HAK_LOG1(hak, log_mask, " h=%zd", io_handle);

				/* dump process IDs waiting for input signaling */
				HAK_LOG0 (hak, log_mask, "(wpi");
				sem = hak->sem_io_tuple[index].sem[HAK_SEMAPHORE_IO_TYPE_INPUT];
				if (sem)
				{
					hak_oop_process_t wp; /* waiting process */
					for (wp = sem->waiting.first; (hak_oop_t)wp != hak->_nil; wp = wp->sem_wait.next)
					{
						HAK_LOG1(hak, log_mask, ":%zd", HAK_OOP_TO_SMOOI(wp->id));
					}
				}
				else
				{
					HAK_LOG0 (hak, log_mask, ":none");
				}

				/* dump process IDs waitingt for output signaling */
				HAK_LOG0 (hak, log_mask, ",wpo");
				sem = hak->sem_io_tuple[index].sem[HAK_SEMAPHORE_IO_TYPE_OUTPUT];
				if (sem)
				{
					hak_oop_process_t wp; /* waiting process */
					for (wp = sem->waiting.first; (hak_oop_t)wp != hak->_nil; wp = wp->sem_wait.next)
					{
						HAK_LOG1(hak, log_mask, ":%zd", HAK_OOP_TO_SMOOI(wp->id));
					}
				}
				else
				{
					HAK_LOG0 (hak, log_mask, ":none");
				}

				HAK_LOG0 (hak, log_mask, ")");
			}
		}
		HAK_LOG0 (hak, log_mask, "\n");
	}
}

static HAK_INLINE void reset_process_stack_pointers (hak_t* hak, hak_oop_process_t proc)
{
#if defined(HAK_DEBUG_VM_PROCESSOR)
	HAK_LOG4(hak, HAK_LOG_IC | HAK_LOG_DEBUG,
		"Processor - process[%zd] SP: %zd(%zd) ST: %zd",
		HAK_OOP_TO_SMOOI(proc->id),
		HAK_OOP_TO_SMOOI(proc->sp), HAK_OOP_TO_SMOOI(proc->sp) - (-1), HAK_OOP_TO_SMOOI(proc->st));
	HAK_LOG6(hak, HAK_LOG_IC | HAK_LOG_DEBUG,
		" EXSP: %zd(%zd) EXST: %zd CLSP: %zd(%zd) CLST: %zd\n",
		HAK_OOP_TO_SMOOI(proc->exsp), HAK_OOP_TO_SMOOI(proc->exsp) - HAK_OOP_TO_SMOOI(proc->st), HAK_OOP_TO_SMOOI(proc->exst),
		HAK_OOP_TO_SMOOI(proc->clsp), HAK_OOP_TO_SMOOI(proc->clsp) - HAK_OOP_TO_SMOOI(proc->exst), HAK_OOP_TO_SMOOI(proc->clst));
#endif

	proc->sp = HAK_SMOOI_TO_OOP(-1); /* invalidate the process stack */
	proc->exsp = proc->st;
	proc->clsp = proc->clst;
}

static void terminate_process (hak_t* hak, hak_oop_process_t proc)
{
	if (proc->state == HAK_SMOOI_TO_OOP(HAK_PROCESS_STATE_RUNNING) ||
	    proc->state == HAK_SMOOI_TO_OOP(HAK_PROCESS_STATE_RUNNABLE))
	{
		/* RUNNING/RUNNABLE ---> TERMINATED */
	#if defined(HAK_DEBUG_VM_PROCESSOR)
		HAK_LOG2(hak, HAK_LOG_IC | HAK_LOG_DEBUG, "Processor - process[%zd] %hs->TERMINATED in terminate_process\n", HAK_OOP_TO_SMOOI(proc->id), proc_state_to_string(HAK_OOP_TO_SMOOI(proc->state)));
	#endif

		if (proc == hak->processor->active)
		{
			hak_oop_process_t nrp;

			/* terminating the active process */
			HAK_ASSERT(hak, proc->state == HAK_SMOOI_TO_OOP(HAK_PROCESS_STATE_RUNNING));

			nrp = find_next_runnable_process(hak);

			STORE_ACTIVE_SP(hak); /* commit the stack pointer before termination */

			unchain_from_processor(hak, proc, HAK_PROCESS_STATE_TERMINATED);
			reset_process_stack_pointers(hak, proc); /* invalidate the process stack */
			proc->current_context = proc->initial_context; /* not needed but just in case */
			/* a runnable or running process must not be chanined to the
			 * process list of a semaphore */
			HAK_ASSERT(hak, (hak_oop_t)proc->sem == hak->_nil);

			if (nrp == proc)
			{
				/* no runnable process after termination */
				HAK_ASSERT(hak, hak->processor->active == hak->nil_process);
				if (HAK_LOG_ENABLED(hak, HAK_LOG_IC | HAK_LOG_DEBUG))
				{
					HAK_LOG5(hak, HAK_LOG_IC | HAK_LOG_DEBUG,
						"No runnable process after termination of process %zd - total %zd runnable/running %zd suspended %zd - sem_io_wait_count %zu\n",
						HAK_OOP_TO_SMOOI(proc->id),
						HAK_OOP_TO_SMOOI(hak->processor->total_count),
						HAK_OOP_TO_SMOOI(hak->processor->runnable.count),
						HAK_OOP_TO_SMOOI(hak->processor->suspended.count),
						hak->sem_io_wait_count
					);

					dump_process_info(hak, HAK_LOG_IC | HAK_LOG_DEBUG);
				}
			}
			else
			{
				/* there are other processes to schedule */
				switch_to_process(hak, nrp, HAK_PROCESS_STATE_TERMINATED);
			}
		}
		else
		{
			/* termiante a runnable process which is not an actively running process */
			HAK_ASSERT(hak, proc->state == HAK_SMOOI_TO_OOP(HAK_PROCESS_STATE_RUNNABLE));
			unchain_from_processor(hak, proc, HAK_PROCESS_STATE_TERMINATED);
			reset_process_stack_pointers(hak, proc); /* invalidate the process stack */
		}

		/* when terminated, clear it from the pid table and set the process id to a negative number */
		free_pid(hak, proc);
	}
	else if (proc->state == HAK_SMOOI_TO_OOP(HAK_PROCESS_STATE_SUSPENDED))
	{
		/* SUSPENDED ---> TERMINATED */
	#if defined(HAK_DEBUG_VM_PROCESSOR)
		HAK_LOG2(hak, HAK_LOG_IC | HAK_LOG_DEBUG, "Processor - process[%zd] %hs->TERMINATED in terminate_process\n", HAK_OOP_TO_SMOOI(proc->id), proc_state_to_string(HAK_OOP_TO_SMOOI(proc->state)));
	#endif

		/*proc->state = HAK_SMOOI_TO_OOP(HAK_PROCESS_STATE_TERMINATED);*/
		unchain_from_processor(hak, proc, HAK_PROCESS_STATE_TERMINATED);
		reset_process_stack_pointers(hak, proc); /* invalidate the process stack */

		if ((hak_oop_t)proc->sem != hak->_nil)
		{
			if (HAK_IS_SEMAPHORE_GROUP(hak, proc->sem))
			{
				if (HAK_OOP_TO_SMOOI(((hak_oop_semaphore_group_t)proc->sem)->sem_io_count) > 0)
				{
					HAK_ASSERT(hak, hak->sem_io_wait_count > 0);
					hak->sem_io_wait_count--;
					HAK_DEBUG1 (hak, "terminate_process(sg) - lowered sem_io_wait_count to %zu\n", hak->sem_io_wait_count);
				}
			}
			else
			{
				HAK_ASSERT(hak, HAK_IS_SEMAPHORE(hak, proc->sem));
				if (((hak_oop_semaphore_t)proc->sem)->subtype == HAK_SMOOI_TO_OOP(HAK_SEMAPHORE_SUBTYPE_IO))
				{
					HAK_ASSERT(hak, hak->sem_io_wait_count > 0);
					hak->sem_io_wait_count--;
					HAK_DEBUG3 (hak, "terminate_process(s) - lowered sem_io_wait_count to %zu for IO semaphore at index %zd handle %zd\n",
						hak->sem_io_wait_count,
						HAK_OOP_TO_SMOOI(((hak_oop_semaphore_t)proc->sem)->u.io.index),
						HAK_OOP_TO_SMOOI(((hak_oop_semaphore_t)proc->sem)->u.io.handle)
					);
				}
			}

			unchain_from_semaphore(hak, proc);
		}

		/* when terminated, clear it from the pid table and set the process id to a negative number */
		free_pid(hak, proc);
	}
#if 0
	else if (proc->state == HAK_SMOOI_TO_OOP(HAK_PROCESS_STATE_WAITING))
	{
		/* WAITING ---> TERMINATED */
		/* TODO: */
	}
#endif
}

static void terminate_all_processes (hak_t* hak)
{
	while (HAK_OOP_TO_SMOOI(hak->processor->suspended.count) > 0)
	{
		terminate_process(hak, hak->processor->suspended.first);
	}

	while (HAK_OOP_TO_SMOOI(hak->processor->runnable.count) > 0)
	{
		terminate_process(hak, hak->processor->runnable.first);
	}

	HAK_ASSERT(hak, HAK_OOP_TO_SMOOI(hak->processor->total_count) == 0);
	HAK_ASSERT(hak, hak->processor->active == hak->nil_process);
}

static void resume_process (hak_t* hak, hak_oop_process_t proc)
{
	if (proc->state == HAK_SMOOI_TO_OOP(HAK_PROCESS_STATE_SUSPENDED))
	{
		/* SUSPENDED ---> RUNNABLE */
		/*HAK_ASSERT(hak, (hak_oop_t)proc->ps.prev == hak->_nil);
		HAK_ASSERT(hak, (hak_oop_t)proc->ps.next == hak->_nil);*/

	#if defined(HAK_DEBUG_VM_PROCESSOR)
		HAK_LOG2(hak, HAK_LOG_IC | HAK_LOG_DEBUG, "Processor - process[%zd] %hs->RUNNABLE in resume_process\n", HAK_OOP_TO_SMOOI(proc->id), proc_state_to_string(HAK_OOP_TO_SMOOI(proc->state)));
	#endif

		/* don't switch to this process. just change the state to RUNNABLE.
		 * process switching should be triggerd by the process scheduler. */
		chain_into_processor(hak, proc, HAK_PROCESS_STATE_RUNNABLE);
		/*proc->current_context = proc->initial_context;*/
	}
#if 0
	else if (proc->state == HAK_SMOOI_TO_OOP(HAK_PROCESS_STATE_RUNNABLE))
	{
		/* RUNNABLE ---> RUNNING */
		/* TODO: should i allow this? */
		HAK_ASSERT(hak, hak->processor->active != proc);
		switch_to_process(hak, proc, HAK_PROCESS_STATE_RUNNABLE);
	}
#endif
}

static void suspend_process (hak_t* hak, hak_oop_process_t proc)
{
	if (proc->state == HAK_SMOOI_TO_OOP(HAK_PROCESS_STATE_RUNNING) ||
	    proc->state == HAK_SMOOI_TO_OOP(HAK_PROCESS_STATE_RUNNABLE))
	{
		/* RUNNING/RUNNABLE ---> SUSPENDED */

	#if defined(HAK_DEBUG_VM_PROCESSOR)
		HAK_LOG2(hak, HAK_LOG_IC | HAK_LOG_DEBUG, "Processor - process[%zd] %hs->SUSPENDED in suspend_process\n", HAK_OOP_TO_SMOOI(proc->id), proc_state_to_string(HAK_OOP_TO_SMOOI(proc->state)));
	#endif

		if (proc == hak->processor->active)
		{
			/* suspend the active process */
			hak_oop_process_t nrp;

			nrp = find_next_runnable_process(hak);
			if (nrp == proc)
			{
				/* no runnable process after suspension */
				sleep_active_process(hak, HAK_PROCESS_STATE_RUNNABLE);
				unchain_from_processor(hak, proc, HAK_PROCESS_STATE_SUSPENDED);

				/* the last running/runnable process has been unchained
				 * from the processor and set to SUSPENDED. the active
				 * process must be the nil process */
				HAK_ASSERT(hak, hak->processor->active == hak->nil_process);
			}
			else
			{
				/* unchain_from_processor moves the process to the suspended
				 * process and sets its state to the given state(SUSPENDED here).
				 * it doesn't change the active process. we switch the active
				 * process with switch_to_process(). setting the state of the
				 * old active process to SUSPENDED is redundant because it's
				 * done in unchain_from_processor(). the state of the active
				 * process is somewhat wrong for a short period of time until
				 * switch_to_process() has changed the active process. */
				unchain_from_processor(hak, proc, HAK_PROCESS_STATE_SUSPENDED);
				HAK_ASSERT(hak, hak->processor->active != hak->nil_process);
				switch_to_process(hak, nrp, HAK_PROCESS_STATE_SUSPENDED);
			}
		}
		else
		{
			unchain_from_processor(hak, proc, HAK_PROCESS_STATE_SUSPENDED);
		}
	}
}

static void yield_process (hak_t* hak, hak_oop_process_t proc)
{
	if (proc->state == HAK_SMOOI_TO_OOP(HAK_PROCESS_STATE_RUNNING))
	{
		/* RUNNING --> RUNNABLE */
		hak_oop_process_t nrp;

		HAK_ASSERT(hak, proc == hak->processor->active);
		HAK_ASSERT(hak, HAK_IS_PROCESS(hak, proc));

		nrp = find_next_runnable_process(hak);
		/* if there are more than 1 runnable processes, the next
		 * runnable process must be different from proc */
		if (nrp != proc)
		{
		#if defined(HAK_DEBUG_VM_PROCESSOR)
			HAK_LOG2(hak, HAK_LOG_IC | HAK_LOG_DEBUG, "Processor - process[%zd] %hs->RUNNABLE in yield_process\n", HAK_OOP_TO_SMOOI(proc->id), proc_state_to_string(HAK_OOP_TO_SMOOI(proc->state)));
		#endif
			switch_to_process(hak, nrp, HAK_PROCESS_STATE_RUNNABLE);
		}
	}
}


static int async_signal_semaphore (hak_t* hak, hak_oop_semaphore_t sem)
{
#if 0
	if (hak->sem_list_count >= SEM_LIST_MAX)
	{
		hak_seterrnum(hak, HAK_ESLFULL);
		return -1;
	}

	if (hak->sem_list_count >= hak->sem_list_capa)
	{
		hak_oow_t new_capa;
		hak_oop_semaphore_t* tmp;

		new_capa = hak->sem_list_capa + SEM_LIST_INC; /* TODO: overflow check.. */
		tmp = (hak_oop_semaphore_t*)hak_reallocmem(hak, hak->sem_list, HAK_SIZEOF(hak_oop_semaphore_t) * new_capa);
		if (HAK_UNLIKELY(!tmp)) return -1;

		hak->sem_list = tmp;
		hak->sem_list_capa = new_capa;
	}

	hak->sem_list[hak->sem_list_count] = sem;
	hak->sem_list_count++;
#endif
	return 0;
}

static hak_oop_process_t signal_semaphore (hak_t* hak, hak_oop_semaphore_t sem)
{
	hak_oop_process_t proc;
	hak_ooi_t count;
	hak_oop_semaphore_group_t sg;

	sg = sem->group;
	if ((hak_oop_t)sg != hak->_nil)
	{
		/* the semaphore belongs to a semaphore group */
		if ((hak_oop_t)sg->waiting.first != hak->_nil)
		{
			hak_ooi_t sp;

			/* there is a process waiting on the process group */
			proc = sg->waiting.first; /* will wake the first process in the waiting list */

			unchain_from_semaphore(hak, proc);
			resume_process(hak, proc);

			/* [IMPORTANT] RETURN VALUE of SemaphoreGroup's wait.
			 * ------------------------------------------------------------
			 * the waiting process has been suspended after a waiting
			 * primitive function in Semaphore or SemaphoreGroup.
			 * the top of the stack of the process must hold the temporary
			 * return value set by await_semaphore() or await_semaphore_group().
			 * change the return value forcibly to the actual signaled
			 * semaphore */
			HAK_ASSERT(hak, HAK_OOP_TO_SMOOI(proc->sp) < (hak_ooi_t)(HAK_OBJ_GET_SIZE(proc) - HAK_PROCESS_NAMED_INSTVARS));
			sp = HAK_OOP_TO_SMOOI(proc->sp);
			proc->slot[sp] = (hak_oop_t)sem;

			/* i should decrement the counter as long as the group being
			 * signaled contains an IO semaphore */
			if (HAK_OOP_TO_SMOOI(sg->sem_io_count) > 0)
			{
				HAK_ASSERT(hak, hak->sem_io_wait_count > 0);
				hak->sem_io_wait_count--;
				HAK_DEBUG2 (hak, "signal_semaphore(sg) - lowered sem_io_wait_count to %zu for handle %zd\n", hak->sem_io_wait_count, HAK_OOP_TO_SMOOI(sem->u.io.handle));
			}
			return proc;
		}
	}

	/* if the semaphore belongs to a semaphore group and the control reaches
	 * here, no process is waiting on the semaphore group. however, a process
	 * may still be waiting on the semaphore. If a process waits on a semaphore
	 * group and another process wait on a semaphore that belongs to the
	 * semaphore group, the process waiting on the group always wins.
	 *
	 *    TODO: implement a fair scheduling policy. or do i simply have to disallow individual wait on a semaphore belonging to a group?
	 *
	 * if it doesn't belong to a sempahore group, i'm free from the starvation issue.
	 */
	if ((hak_oop_t)sem->waiting.first == hak->_nil)
	{
		/* no process is waiting on this semaphore */

		count = HAK_OOP_TO_SMOOI(sem->count);
		count++;
		sem->count = HAK_SMOOI_TO_OOP(count);

		HAK_ASSERT(hak, count >= 1);
		if (count == 1 && (hak_oop_t)sg != hak->_nil)
		{
			/* move the semaphore from the unsignaled list to the signaled list
			 * if the semaphore count has changed from 0 to 1 in a group */
			HAK_DELETE_FROM_OOP_LIST(hak, &sg->sems[HAK_SEMAPHORE_GROUP_SEMS_UNSIG], sem, grm);
			HAK_APPEND_TO_OOP_LIST(hak, &sg->sems[HAK_SEMAPHORE_GROUP_SEMS_SIG], hak_oop_semaphore_t, sem, grm);
		}

		/* no process has been resumed */
		return (hak_oop_process_t)hak->_nil;
	}
	else
	{
		proc = sem->waiting.first;

		/* [NOTE] no GC must occur as 'proc' isn't protected with hak_pushvolat(). */

		/* detach a process from a semaphore's waiting list and
		 * make it runnable */
		unchain_from_semaphore(hak, proc);
		resume_process(hak, proc);

		if (sem->subtype == HAK_SMOOI_TO_OOP(HAK_SEMAPHORE_SUBTYPE_IO))
		{
			HAK_ASSERT(hak, hak->sem_io_wait_count > 0);
			hak->sem_io_wait_count--;
			HAK_DEBUG3 (hak, "signal_semaphore(s) - lowered sem_io_wait_count to %zu for IO semaphore at index %zd handle %zd\n",
				hak->sem_io_wait_count, HAK_OOP_TO_SMOOI(sem->u.io.index), HAK_OOP_TO_SMOOI(sem->u.io.handle));
		}

		/* return the resumed(runnable) process */
		return proc;
	}
}

static HAK_INLINE int can_await_semaphore (hak_t* hak, hak_oop_semaphore_t sem)
{
	/* a sempahore that doesn't belong to a gruop can be waited on */
	return (hak_oop_t)sem->group == hak->_nil;
}

static HAK_INLINE void await_semaphore (hak_t* hak, hak_oop_semaphore_t sem)
{
	hak_oop_process_t proc;
	hak_ooi_t count;
	hak_oop_semaphore_group_t semgrp;

	semgrp = sem->group;

	/* the caller of this function must ensure that the semaphore doesn't belong to a group */
	HAK_ASSERT(hak, (hak_oop_t)semgrp == hak->_nil);

	count = HAK_OOP_TO_SMOOI(sem->count);
	if (count > 0)
	{
		/* it's already signaled */
		count--;
		sem->count = HAK_SMOOI_TO_OOP(count);

		if ((hak_oop_t)semgrp != hak->_nil && count == 0)
		{

			int sems_idx;
			/* TODO: if i disallow individual wait on a semaphore in a group,
			 *       this membership manipulation is redundant */
			HAK_DELETE_FROM_OOP_LIST(hak, &semgrp->sems[HAK_SEMAPHORE_GROUP_SEMS_SIG], sem, grm);
			sems_idx = count > 0? HAK_SEMAPHORE_GROUP_SEMS_SIG: HAK_SEMAPHORE_GROUP_SEMS_UNSIG;
			HAK_APPEND_TO_OOP_LIST(hak, &semgrp->sems[sems_idx], hak_oop_semaphore_t, sem, grm);
		}
	}
	else
	{
		/* not signaled. need to wait */
		proc = hak->processor->active;

		/* suspend the active process */
		suspend_process(hak, proc);

		/* link the suspended process to the semaphore's process list */
		chain_into_semaphore(hak, proc, sem);

		HAK_ASSERT(hak, sem->waiting.last == proc);

		if (sem->subtype == HAK_SMOOI_TO_OOP(HAK_SEMAPHORE_SUBTYPE_IO))
		{
			hak->sem_io_wait_count++;
			HAK_DEBUG3 (hak, "await_semaphore - raised sem_io_wait_count to %zu for IO semaphore at index %zd handle %zd\n",
				hak->sem_io_wait_count, HAK_OOP_TO_SMOOI(sem->u.io.index), HAK_OOP_TO_SMOOI(sem->u.io.handle));
		}

		HAK_ASSERT(hak, hak->processor->active != proc);
	}
}

static HAK_INLINE hak_oop_t await_semaphore_group (hak_t* hak, hak_oop_semaphore_group_t semgrp)
{
	/* wait for one of semaphores in the group to be signaled */

	hak_oop_process_t proc;
	hak_oop_semaphore_t sem;

	HAK_ASSERT(hak, HAK_IS_SEMAPHORE_GROUP(hak, semgrp));

	if (HAK_OOP_TO_SMOOI(semgrp->sem_count) <= 0)
	{
		/* cannot wait on a semaphore group that has no member semaphores.
		 * return failure if waiting on such a semapohre group is attempted */
		HAK_ASSERT(hak, (hak_oop_t)semgrp->sems[HAK_SEMAPHORE_GROUP_SEMS_SIG].first == hak->_nil);
		HAK_ASSERT(hak, (hak_oop_t)semgrp->sems[HAK_SEMAPHORE_GROUP_SEMS_SIG].last == hak->_nil);
		return HAK_ERROR_TO_OOP(HAK_EINVAL); /* TODO: better error code? */
	}

	sem = semgrp->sems[HAK_SEMAPHORE_GROUP_SEMS_SIG].first;
	if ((hak_oop_t)sem != hak->_nil)
	{
		hak_ooi_t count;
		int sems_idx;

		/* there is a semaphore signaled in the group */
		count = HAK_OOP_TO_SMOOI(sem->count);
		HAK_ASSERT(hak, count > 0);
		count--;
		sem->count = HAK_SMOOI_TO_OOP(count);

		HAK_DELETE_FROM_OOP_LIST(hak, &semgrp->sems[HAK_SEMAPHORE_GROUP_SEMS_SIG], sem, grm);
		sems_idx = count > 0? HAK_SEMAPHORE_GROUP_SEMS_SIG: HAK_SEMAPHORE_GROUP_SEMS_UNSIG;
		HAK_APPEND_TO_OOP_LIST(hak, &semgrp->sems[sems_idx], hak_oop_semaphore_t, sem, grm);

		return (hak_oop_t)sem;
	}

	/* no semaphores have been signaled. suspend the current process
	 * until at least one of them is signaled */
	proc = hak->processor->active;

	/* suspend the active process */
	suspend_process(hak, proc);

	/* link the suspended process to the semaphore group's process list */
	chain_into_semaphore(hak, proc, (hak_oop_semaphore_t)semgrp);

	HAK_ASSERT(hak, semgrp->waiting.last == proc);

	if (HAK_OOP_TO_SMOOI(semgrp->sem_io_count) > 0)
	{
		/* there might be more than 1 IO semaphores in the group
		 * but i increment hak->sem_io_wait_count by 1 only */
		hak->sem_io_wait_count++;
		HAK_DEBUG1 (hak, "await_semaphore_group - raised sem_io_wait_count to %zu\n", hak->sem_io_wait_count);
	}

	/* the current process will get suspended after the caller (mostly a
	 * a primitive function handler) is over as it's added to a suspened
	 * process list above */
	HAK_ASSERT(hak, hak->processor->active != proc);
	return hak->_nil;
}

static void sift_up_sem_heap (hak_t* hak, hak_ooi_t index)
{
	if (index > 0)
	{
		hak_ooi_t parent;
		hak_oop_semaphore_t sem, parsem;

		parent = SEM_HEAP_PARENT(index);
		sem = hak->sem_heap[index];
		parsem = hak->sem_heap[parent];
		if (SEM_HEAP_EARLIER_THAN(hak, sem, parsem))
		{
			do
			{
				/* move down the parent to the current position */
				parsem->u.timed.index = HAK_SMOOI_TO_OOP(index);
				hak->sem_heap[index] = parsem;

				/* traverse up */
				index = parent;
				if (index <= 0) break;

				parent = SEM_HEAP_PARENT(parent);
				parsem = hak->sem_heap[parent];
			}
			while (SEM_HEAP_EARLIER_THAN(hak, sem, parsem));

			sem->u.timed.index = HAK_SMOOI_TO_OOP(index);
			hak->sem_heap[index] = sem;
		}
	}
}

static void sift_down_sem_heap (hak_t* hak, hak_ooi_t index)
{
	hak_ooi_t base = hak->sem_heap_count / 2;

	if (index < base) /* at least 1 child is under the 'index' position */
	{
		hak_ooi_t left, right, child;
		hak_oop_semaphore_t sem, chisem;

		sem = hak->sem_heap[index];
		do
		{
			left = SEM_HEAP_LEFT(index);
			right = SEM_HEAP_RIGHT(index);

			if (right < hak->sem_heap_count && SEM_HEAP_EARLIER_THAN(hak, hak->sem_heap[right], hak->sem_heap[left]))
			{
				child = right;
			}
			else
			{
				child = left;
			}

			chisem = hak->sem_heap[child];
			if (SEM_HEAP_EARLIER_THAN(hak, sem, chisem)) break;

			chisem->u.timed.index = HAK_SMOOI_TO_OOP(index);
			hak->sem_heap[index] = chisem;

			index = child;
		}
		while (index < base);

		sem->u.timed.index = HAK_SMOOI_TO_OOP(index);
		hak->sem_heap[index] = sem;
	}
}

static int add_to_sem_heap (hak_t* hak, hak_oop_semaphore_t sem)
{
	hak_ooi_t index;

	HAK_ASSERT(hak, sem->subtype == hak->_nil);

	if (hak->sem_heap_count >= SEM_HEAP_MAX)
	{
		hak_seterrbfmt(hak, HAK_ESEMFLOOD, "too many semaphores in the semaphore heap");
		return -1;
	}

	if (hak->sem_heap_count >= hak->sem_heap_capa)
	{
		hak_oow_t new_capa;
		hak_oop_semaphore_t* tmp;

		/* no overflow check when calculating the new capacity
		 * owing to SEM_HEAP_MAX check above */
		new_capa = hak->sem_heap_capa + SEM_HEAP_INC;
		tmp = (hak_oop_semaphore_t*)hak_reallocmem(hak, hak->sem_heap, HAK_SIZEOF(hak_oop_semaphore_t) * new_capa);
		if (HAK_UNLIKELY(!tmp)) return -1;

		hak->sem_heap = tmp;
		hak->sem_heap_capa = new_capa;
	}

	HAK_ASSERT(hak, hak->sem_heap_count <= HAK_SMOOI_MAX);

	index = hak->sem_heap_count;
	hak->sem_heap[index] = sem;
	sem->u.timed.index = HAK_SMOOI_TO_OOP(index);
	sem->subtype = HAK_SMOOI_TO_OOP(HAK_SEMAPHORE_SUBTYPE_TIMED);
	hak->sem_heap_count++;

	sift_up_sem_heap(hak, index);
	return 0;
}

static void delete_from_sem_heap (hak_t* hak, hak_ooi_t index)
{
	hak_oop_semaphore_t sem, lastsem;

	HAK_ASSERT(hak, index >= 0 && index < hak->sem_heap_count);

	sem = hak->sem_heap[index];

	sem->subtype = hak->_nil;
	sem->u.timed.index = hak->_nil;
	sem->u.timed.ftime_sec = hak->_nil;
	sem->u.timed.ftime_nsec = hak->_nil;

	hak->sem_heap_count--;
	if (/*hak->sem_heap_count > 0 &&*/ index != hak->sem_heap_count)
	{
		/* move the last item to the deletion position */
		lastsem = hak->sem_heap[hak->sem_heap_count];
		lastsem->u.timed.index = HAK_SMOOI_TO_OOP(index);
		hak->sem_heap[index] = lastsem;

		if (SEM_HEAP_EARLIER_THAN(hak, lastsem, sem))
			sift_up_sem_heap(hak, index);
		else
			sift_down_sem_heap(hak, index);
	}
}

#if 0
/* unused */
static void update_sem_heap (hak_t* hak, hak_ooi_t index, hak_oop_semaphore_t newsem)
{
	hak_oop_semaphore_t sem;

	sem = hak->sem_heap[index];
	sem->timed.index = hak->_nil;

	newsem->timed.index = HAK_SMOOI_TO_OOP(index);
	hak->sem_heap[index] = newsem;

	if (SEM_HEAP_EARLIER_THAN(hak, newsem, sem))
		sift_up_sem_heap(hak, index);
	else
		sift_down_sem_heap(hak, index);
}
#endif

static int add_sem_to_sem_io_tuple (hak_t* hak, hak_oop_semaphore_t sem, hak_ooi_t io_handle, hak_semaphore_io_type_t io_type)
{
	hak_ooi_t index;
	hak_ooi_t new_mask;
	int n, tuple_added = 0;

	HAK_ASSERT(hak, sem->subtype == (hak_oop_t)hak->_nil);
	HAK_ASSERT(hak, sem->u.io.index == (hak_oop_t)hak->_nil);
	/*HAK_ASSERT(hak, sem->io.handle == (hak_oop_t)hak->_nil);
	HAK_ASSERT(hak, sem->io.type == (hak_oop_t)hak->_nil);*/

	if (io_handle < 0)
	{
		hak_seterrbfmt(hak, HAK_EINVAL, "handle %zd out of supported range", io_handle);
		return -1;
	}

	if (io_handle >= hak->sem_io_map_capa)
	{
		hak_oow_t new_capa, i;
		hak_ooi_t* tmp;

/* TODO: specify the maximum io_handle supported and check it here instead of just relying on memory allocation success/failure? */
		new_capa = HAK_ALIGN_POW2(io_handle + 1, SEM_IO_MAP_ALIGN);

		tmp = (hak_ooi_t*)hak_reallocmem(hak, hak->sem_io_map, HAK_SIZEOF(*tmp) * new_capa);
		if (HAK_UNLIKELY(!tmp))
		{
			const hak_ooch_t* oldmsg = hak_backuperrmsg(hak);
			hak_seterrbfmt(hak, hak->errnum, "handle %zd out of supported range - %js", oldmsg);
			return -1;
		}

		for (i = hak->sem_io_map_capa; i < new_capa; i++) tmp[i] = -1;

		hak->sem_io_map = tmp;
		hak->sem_io_map_capa = new_capa;
	}

	index = hak->sem_io_map[io_handle];
	if (index <= -1)
	{
		/* this handle is not in any tuples. add it to a new tuple */
		if (hak->sem_io_tuple_count >= SEM_IO_TUPLE_MAX)
		{
			hak_seterrbfmt(hak, HAK_ESEMFLOOD, "too many IO semaphore tuples");
			return -1;
		}

		if (hak->sem_io_tuple_count >= hak->sem_io_tuple_capa)
		{
			hak_oow_t new_capa;
			hak_sem_tuple_t* tmp;

			/* no overflow check when calculating the new capacity
			 * owing to SEM_IO_TUPLE_MAX check above */
			new_capa = hak->sem_io_tuple_capa + SEM_IO_TUPLE_INC;
			tmp = (hak_sem_tuple_t*)hak_reallocmem(hak, hak->sem_io_tuple, HAK_SIZEOF(hak_sem_tuple_t) * new_capa);
			if (HAK_UNLIKELY(!tmp)) return -1;

			hak->sem_io_tuple = tmp;
			hak->sem_io_tuple_capa = new_capa;
		}

		/* this condition must be true assuming SEM_IO_TUPLE_MAX <= HAK_SMOOI_MAX */
		HAK_ASSERT(hak, hak->sem_io_tuple_count <= HAK_SMOOI_MAX);
		index = hak->sem_io_tuple_count;

		tuple_added = 1;

		/* safe to initialize before vm_muxadd() because
		 * hak->sem_io_tuple_count has not been incremented.
		 * still no impact even if it fails. */
		hak->sem_io_tuple[index].sem[HAK_SEMAPHORE_IO_TYPE_INPUT] = HAK_NULL;
		hak->sem_io_tuple[index].sem[HAK_SEMAPHORE_IO_TYPE_OUTPUT] = HAK_NULL;
		hak->sem_io_tuple[index].handle = io_handle;
		hak->sem_io_tuple[index].mask = 0;

		new_mask = ((hak_ooi_t)1 << io_type);

		hak_pushvolat(hak, (hak_oop_t*)&sem);
		n = hak->vmprim.vm_muxadd(hak, io_handle, new_mask);
		hak_popvolat(hak);
	}
	else
	{
		if (hak->sem_io_tuple[index].sem[io_type])
		{
			hak_seterrbfmt(hak, HAK_EINVAL, "handle %zd already linked with an IO semaphore for %hs", io_handle, io_type_str[io_type]);
			return -1;
		}

		new_mask = hak->sem_io_tuple[index].mask; /* existing mask */
		new_mask |= ((hak_ooi_t)1 << io_type);

		hak_pushvolat(hak, (hak_oop_t*)&sem);
		n = hak->vmprim.vm_muxmod(hak, io_handle, new_mask);
		hak_popvolat(hak);
	}

	if (n <= -1)
	{
		HAK_LOG3(hak, HAK_LOG_WARN, "Failed to add IO semaphore at index %zd for %hs on handle %zd\n", index, io_type_str[io_type], io_handle);
		return -1;
	}

	HAK_LOG3(hak, HAK_LOG_DEBUG, "Added IO semaphore at index %zd for %hs on handle %zd\n", index, io_type_str[io_type], io_handle);

	sem->subtype = HAK_SMOOI_TO_OOP(HAK_SEMAPHORE_SUBTYPE_IO);
	sem->u.io.index = HAK_SMOOI_TO_OOP(index);
	sem->u.io.handle = HAK_SMOOI_TO_OOP(io_handle);
	sem->u.io.type = HAK_SMOOI_TO_OOP((hak_ooi_t)io_type);

	hak->sem_io_tuple[index].handle = io_handle;
	hak->sem_io_tuple[index].mask = new_mask;
	hak->sem_io_tuple[index].sem[io_type] = sem;

	hak->sem_io_count++;
	if (tuple_added)
	{
		hak->sem_io_tuple_count++;
		hak->sem_io_map[io_handle] = index;
	}

	/* update the number of IO semaphores in a group if necessary */
	if ((hak_oop_t)sem->group != hak->_nil)
	{
		hak_ooi_t count;
		count = HAK_OOP_TO_SMOOI(sem->group->sem_io_count);
		count++;
		sem->group->sem_io_count = HAK_SMOOI_TO_OOP(count);
	}

	return 0;
}

static int delete_sem_from_sem_io_tuple (hak_t* hak, hak_oop_semaphore_t sem, int force)
{
	hak_ooi_t index;
	hak_ooi_t new_mask, io_handle, io_type;
	int x;

	HAK_ASSERT(hak, sem->subtype == HAK_SMOOI_TO_OOP(HAK_SEMAPHORE_SUBTYPE_IO));
	HAK_ASSERT(hak, HAK_OOP_IS_SMOOI(sem->u.io.type));
	HAK_ASSERT(hak, HAK_OOP_IS_SMOOI(sem->u.io.index));
	HAK_ASSERT(hak, HAK_OOP_IS_SMOOI(sem->u.io.handle));

	index = HAK_OOP_TO_SMOOI(sem->u.io.index);
	HAK_ASSERT(hak, index >= 0 && index < hak->sem_io_tuple_count);

	io_handle = HAK_OOP_TO_SMOOI(sem->u.io.handle);
	if (io_handle < 0 || io_handle >= hak->sem_io_map_capa)
	{
		hak_seterrbfmt(hak, HAK_EINVAL, "handle %zd out of supported range", io_handle);
		return -1;
	}
	HAK_ASSERT(hak, hak->sem_io_map[io_handle] == HAK_OOP_TO_SMOOI(sem->u.io.index));

	io_type = HAK_OOP_TO_SMOOI(sem->u.io.type);

	new_mask = hak->sem_io_tuple[index].mask;
	new_mask &= ~((hak_ooi_t)1 << io_type); /* this is the new mask after deletion */

	hak_pushvolat(hak, (hak_oop_t*)&sem);
	x = new_mask? hak->vmprim.vm_muxmod(hak, io_handle, new_mask):
	              hak->vmprim.vm_muxdel(hak, io_handle);
	hak_popvolat(hak);
	if (x <= -1)
	{
		HAK_LOG3(hak, HAK_LOG_WARN, "Failed to delete IO semaphore at index %zd handle %zd for %hs\n", index, io_handle, io_type_str[io_type]);
		if (!force) return -1;

		/* [NOTE]
		 *   this means there could be some issue handling the file handles.
		 *   the file handle might have been closed before reaching here.
		 *   assuming the callback works correctly, it's not likely that the
		 *   underlying operating system returns failure for no reason.
		 *   i should inspect the overall vm implementation */
		HAK_LOG1(hak, HAK_LOG_ERROR, "Forcibly unmapping the IO semaphored handle %zd as if it's deleted\n", io_handle);
	}
	else
	{
		HAK_LOG3(hak, HAK_LOG_DEBUG, "Deleted IO semaphore at index %zd handle %zd for %hs\n", index, io_handle, io_type_str[io_type]);
	}

	sem->subtype = hak->_nil;
	sem->u.io.index = hak->_nil;
	sem->u.io.handle = hak->_nil;
	sem->u.io.type = hak->_nil;
	hak->sem_io_count--;

	if ((hak_oop_t)sem->group != hak->_nil)
	{
		hak_ooi_t count;
		count = HAK_OOP_TO_SMOOI(sem->group->sem_io_count);
		HAK_ASSERT(hak, count > 0);
		count--;
		sem->group->sem_io_count = HAK_SMOOI_TO_OOP(count);
	}

	if (new_mask)
	{
		hak->sem_io_tuple[index].mask = new_mask;
		hak->sem_io_tuple[index].sem[io_type] = HAK_NULL;
	}
	else
	{
		hak->sem_io_tuple_count--;

		if (/*hak->sem_io_tuple_count > 0 &&*/ index != hak->sem_io_tuple_count)
		{
			/* migrate the last item to the deleted slot to compact the gap */
			hak->sem_io_tuple[index] = hak->sem_io_tuple[hak->sem_io_tuple_count];

			if (hak->sem_io_tuple[index].sem[HAK_SEMAPHORE_IO_TYPE_INPUT])
				hak->sem_io_tuple[index].sem[HAK_SEMAPHORE_IO_TYPE_INPUT]->u.io.index = HAK_SMOOI_TO_OOP(index);
			if (hak->sem_io_tuple[index].sem[HAK_SEMAPHORE_IO_TYPE_OUTPUT])
				hak->sem_io_tuple[index].sem[HAK_SEMAPHORE_IO_TYPE_OUTPUT]->u.io.index = HAK_SMOOI_TO_OOP(index);

			hak->sem_io_map[hak->sem_io_tuple[index].handle] = index;

			HAK_LOG2(hak, HAK_LOG_DEBUG, "Migrated IO semaphore tuple from index %zd to %zd\n", hak->sem_io_tuple_count, index);
		}

		hak->sem_io_map[io_handle] = -1;
	}

	return 0;
}

static void _signal_io_semaphore (hak_t* hak, hak_oop_semaphore_t sem)
{
	hak_oop_process_t proc;

	proc = signal_semaphore(hak, sem);

	if (hak->processor->active == hak->nil_process && (hak_oop_t)proc != hak->_nil)
	{
		/* this is the only runnable process.
		 * switch the process to the running state.
		 * it uses wake_process() instead of
		 * switch_to_process() as there is no running
		 * process at this moment */
		HAK_ASSERT(hak, proc->state == HAK_SMOOI_TO_OOP(HAK_PROCESS_STATE_RUNNABLE));
		HAK_ASSERT(hak, proc == hak->processor->runnable.first);

	#if 0
		wake_process(hak, proc); /* switch to running */
		hak->proc_switched = 1;
	#else
		switch_to_process_from_nil(hak, proc);
	#endif
	}
}

static void signal_io_semaphore (hak_t* hak, hak_ooi_t io_handle, hak_ooi_t mask)
{
	if (io_handle >= 0 && io_handle < hak->sem_io_map_capa && hak->sem_io_map[io_handle] >= 0)
	{
		hak_oop_semaphore_t insem, outsem;
		hak_ooi_t sem_io_index;

		sem_io_index = hak->sem_io_map[io_handle];
		insem = hak->sem_io_tuple[sem_io_index].sem[HAK_SEMAPHORE_IO_TYPE_INPUT];
		outsem = hak->sem_io_tuple[sem_io_index].sem[HAK_SEMAPHORE_IO_TYPE_OUTPUT];

		if (outsem)
		{
			if ((mask & (HAK_SEMAPHORE_IO_MASK_OUTPUT | HAK_SEMAPHORE_IO_MASK_ERROR)) ||
			    (!insem && (mask & HAK_SEMAPHORE_IO_MASK_HANGUP)))
			{
				_signal_io_semaphore(hak, outsem);
			}
		}

		if (insem)
		{
			if (mask & (HAK_SEMAPHORE_IO_MASK_INPUT | HAK_SEMAPHORE_IO_MASK_HANGUP | HAK_SEMAPHORE_IO_MASK_ERROR))
			{
				_signal_io_semaphore(hak, insem);
			}
		}
	}
	else
	{
		/* you may come across this warning message if the multiplexer returned
		 * an IO event */
		HAK_LOG2(hak, HAK_LOG_WARN, "Warning - semaphore signaling requested on an unmapped handle %zd with mask %#zx\n", io_handle, mask);
	}
}

void hak_releaseiohandle (hak_t* hak, hak_ooi_t io_handle)
{
	/* TODO: optimize io semapore unmapping. since i'm to close the handle,
	 *       i don't need to call delete_sem_from_sem_io_tuple() seperately for input
	 *       and output. */
	if (io_handle < hak->sem_io_map_capa)
	{
		hak_ooi_t index;
		hak_oop_semaphore_t sem;

		index = hak->sem_io_map[io_handle];
		if (index >= 0)
		{
			HAK_ASSERT(hak, hak->sem_io_tuple[index].handle == io_handle);
			sem = hak->sem_io_tuple[index].sem[HAK_SEMAPHORE_IO_TYPE_INPUT];
			if (sem)
			{
				HAK_ASSERT(hak, sem->subtype == HAK_SMOOI_TO_OOP(HAK_SEMAPHORE_SUBTYPE_IO));
				delete_sem_from_sem_io_tuple(hak, sem, 0);
			}
		}
	}

	if (io_handle < hak->sem_io_map_capa)
	{
		hak_ooi_t index;
		hak_oop_semaphore_t sem;

		index = hak->sem_io_map[io_handle];
		if (index >= 0)
		{
			HAK_ASSERT(hak, hak->sem_io_tuple[index].handle == io_handle);
			sem = hak->sem_io_tuple[index].sem[HAK_SEMAPHORE_IO_TYPE_OUTPUT];
			if (sem)
			{
				HAK_ASSERT(hak, sem->subtype == HAK_SMOOI_TO_OOP(HAK_SEMAPHORE_SUBTYPE_IO));
				delete_sem_from_sem_io_tuple(hak, sem, 0);
			}
		}
	}
}

/* ------------------------------------------------------------------------- */

static int prepare_new_context (hak_t* hak, hak_oop_block_t op_blk, hak_ooi_t nargs, int nargs_offset, hak_ooi_t req_nrvars, int copy_args, int is_msgsend, hak_ooi_t msg_ivaroff, hak_oop_context_t* pnewctx)
{
	/* prepare a new block context for activation.
	 * the passed block context becomes the base for a new block context. */

	hak_oop_context_t blkctx;
	hak_ooi_t attr_mask;
	hak_ooi_t fblk_nrvars, fblk_nlvars;
	hak_ooi_t fixed_nargs, actual_nargs, excess_nargs;

	/* the receiver must be a block context */
	HAK_ASSERT(hak, HAK_IS_COMPILED_BLOCK(hak, op_blk));

	attr_mask = HAK_OOP_TO_SMOOI(op_blk->attr_mask);

	fblk_nrvars = GET_BLK_MASK_NRVARS(attr_mask);
	fblk_nlvars = GET_BLK_MASK_NLVARS(attr_mask);
	fixed_nargs = GET_BLK_MASK_NARGS(attr_mask);
	actual_nargs = nargs - nargs_offset;
	excess_nargs = actual_nargs - fixed_nargs;

	if (actual_nargs < fixed_nargs || (!GET_BLK_MASK_VA(attr_mask) && actual_nargs > fixed_nargs))
	{
		HAK_LOG3(hak, HAK_LOG_IC | HAK_LOG_ERROR,
			"Error - wrong number of arguments to a block %O - expecting %zd, got %zd\n",
			op_blk, fixed_nargs, actual_nargs);
		hak_seterrbfmt(hak, HAK_ECALLARG, "wrong number of argument passed to function block - %zd expected, %zd passed", fixed_nargs, actual_nargs);
		return -1;
	}

	if (req_nrvars > fblk_nrvars)
	{
		HAK_LOG3(hak, HAK_LOG_IC | HAK_LOG_ERROR,
			"Error - wrong number of returns specified of a block %O - max expected %zd, requested %zd\n",
			op_blk, fblk_nrvars, req_nrvars);
		hak_seterrbfmt(hak, HAK_ECALLRET, "wrong number of returns requested of function block - %zd expected at most, %zd requested", fblk_nrvars, req_nrvars);
		return -1;
	}

	/* create a new block context to clone op_blk */
	hak_pushvolat(hak, (hak_oop_t*)&op_blk);
	blkctx = make_context(hak, fixed_nargs + fblk_nrvars + fblk_nlvars + excess_nargs);
	hak_popvolat(hak);
	if (HAK_UNLIKELY(!blkctx)) return -1;

#if 0
	/* shallow-copy the named part including home, origin, etc. */
	for (i = 0; i < HAK_CONTEXT_NAMED_INSTVARS; i++)
	{
		((hak_oop_oop_t)blkctx)->slot[i] = ((hak_oop_oop_t)op_blk)->slot[i];
	}
#else
	blkctx->ip = op_blk->ip;
	blkctx->req_nrets = HAK_SMOOI_TO_OOP(req_nrvars);
	blkctx->attr_mask = op_blk->attr_mask;
	blkctx->base = op_blk->home->base;
	blkctx->name = op_blk->name;

	if (is_msgsend)
	{
		/*blkctx->home = blkctx;*/ /* itself */
		blkctx->home = op_blk->home;
		blkctx->mthhome = blkctx;
		blkctx->receiver = HAK_STACK_GETRCV(hak, nargs);
		blkctx->ivaroff = HAK_SMOOI_TO_OOP(msg_ivaroff);
	}
	else
	{
		blkctx->home = op_blk->home;
		blkctx->mthhome = (hak_oop_context_t)hak->_nil;
		blkctx->receiver = op_blk->home->receiver;
	#if 0 /* filled by make_context() already */
		blkctx->ivaroff = HAK_SMOOI_TO_OOP(0); /* not useful if it's not message send */
	#endif
	}
#endif

	if (HAK_LIKELY(copy_args))
	{
		hak_ooi_t i, j;

		/* copy the fixed arguments to the beginning of the variable part of the context block */
		for (i = 0, j = nargs_offset; i < fixed_nargs; i++, j++)
		{
			blkctx->slot[i] = HAK_STACK_GETARG(hak, nargs, j);
		}

		/* variable arguments. place them behind after local variables. */
		for (i = fixed_nargs + fblk_nrvars + fblk_nlvars ; j < nargs; i++, j++)
		{
			blkctx->slot[i] = HAK_STACK_GETARG(hak, nargs, j);
		}
	}

	HAK_ASSERT(hak, (hak_oop_t)blkctx->home != hak->_nil); /* if not intial context, the home must not be null */
	HAK_ASSERT(hak, (hak_oop_t)blkctx->sender == hak->_nil); /* the sender is not set. the caller must set this if needed */

	*pnewctx = blkctx;
	return 0;
}

static HAK_INLINE int __activate_block (hak_t* hak, hak_oop_block_t op_blk, hak_ooi_t nargs, hak_ooi_t nrvars, int is_msgsend, hak_ooi_t msg_ivaroff, hak_oop_context_t* pnewctx)
{
	int x;

	HAK_ASSERT(hak, HAK_IS_COMPILED_BLOCK(hak, op_blk));

	x = prepare_new_context(
		hak,
		op_blk,
		nargs, /* nargs */
		0, /* nargs_offset */
		nrvars,
		1, /* copy_args */
		is_msgsend,
		msg_ivaroff,
		pnewctx);
	if (HAK_UNLIKELY(x <= -1)) return -1;

	HAK_STACK_POPS(hak, nargs + 2); /* pop arguments, called block/function/method, and receiver */
	(*pnewctx)->sender = hak->active_context;

	return 0;
}

static HAK_INLINE int activate_block (hak_t* hak, hak_ooi_t nargs, hak_ooi_t nrvars)
{
	hak_oop_block_t op_blk;
	hak_oop_context_t newctx;
	int x;

	op_blk = (hak_oop_block_t)HAK_STACK_GETOP(hak, nargs);
	HAK_ASSERT(hak, HAK_IS_COMPILED_BLOCK(hak, op_blk));

	x = __activate_block(hak, op_blk, nargs, nrvars, 0, 0, &newctx);
	if (HAK_UNLIKELY(x <= -1)) return -1;

	SWITCH_ACTIVE_CONTEXT(hak, newctx);
	return 0;
}

/* ------------------------------------------------------------------------- */

static int __activate_function (hak_t* hak, hak_oop_function_t op_func, hak_ooi_t nargs, hak_oop_context_t* pnewctx)
{
	/* prepare a new block context for activation */

	hak_oop_context_t functx;
	hak_ooi_t i, j;
	hak_ooi_t attr_mask;
	hak_ooi_t nrvars, nlvars, fixed_nargs, actual_nargs, excess_nargs;
	hak_ooi_t nargs_offset = 0;

	/*
	  fun sum (x) {
	    if (< x 2) 1 else { core.+ x (sum (- x 1)) }
	  }
	  printf ">>>> %d\n" (sum 10)
	 */

	HAK_ASSERT(hak, HAK_IS_FUNCTION(hak, op_func));

	attr_mask = HAK_OOP_TO_SMOOI(op_func->attr_mask);
	nrvars = GET_BLK_MASK_NRVARS(attr_mask);
	nlvars = GET_BLK_MASK_NLVARS(attr_mask);
	fixed_nargs = GET_BLK_MASK_NARGS(attr_mask);
	actual_nargs = nargs - nargs_offset;
	excess_nargs = actual_nargs - fixed_nargs;

	if (actual_nargs < fixed_nargs || (!GET_BLK_MASK_VA(attr_mask) && actual_nargs > fixed_nargs))
	{
		HAK_LOG3(hak, HAK_LOG_IC | HAK_LOG_ERROR,
			"Error - wrong number of arguments to a function %O - expecting %zd, got %zd\n",
			op_func, fixed_nargs, nargs);
		hak_seterrnum(hak, HAK_ECALLARG);
		return -1;
	}

	/* create a new block context to clone op_func */
	hak_pushvolat(hak, (hak_oop_t*)&op_func);
	functx = make_context(hak, fixed_nargs + nrvars + nlvars + excess_nargs);
	hak_popvolat(hak);
	if (HAK_UNLIKELY(!functx)) return -1;

	functx->ip = HAK_SMOOI_TO_OOP(0);
	functx->req_nrets = HAK_SMOOI_TO_OOP(1);
	functx->attr_mask = op_func->attr_mask;
	functx->base = op_func;
	functx->home = op_func->home;
	functx->receiver = HAK_STACK_GETRCV(hak, nargs);

	/* copy the fixed arguments to the beginning of the variable part of the context block */
	for (i = 0, j = nargs_offset; i < fixed_nargs; i++, j++)
	{
		functx->slot[i] = HAK_STACK_GETARG(hak, nargs, j);
	}

	/* variable arguments. place them behind after local variables. */
	for (i = fixed_nargs + nrvars + nlvars ; j < nargs; i++, j++)
	{
		functx->slot[i] = HAK_STACK_GETARG(hak, nargs, j);
	}

	HAK_STACK_POPS(hak, nargs + 2); /* pop arguments, called function/block/method, and receiver */

	HAK_ASSERT(hak, (hak_oop_t)functx->home != hak->_nil);
	functx->sender = hak->active_context;

	*pnewctx = functx;
	return 0;
}

static HAK_INLINE int activate_function (hak_t* hak, hak_ooi_t nargs)
{
	int x;
	hak_oop_function_t op_func;
	hak_oop_context_t newctx;

	op_func = (hak_oop_function_t)HAK_STACK_GETOP(hak, nargs);
	HAK_ASSERT(hak, HAK_IS_FUNCTION(hak, op_func));

	x = __activate_function(hak, op_func, nargs, &newctx);
	if (HAK_UNLIKELY(x <= -1)) return -1;

	SWITCH_ACTIVE_CONTEXT(hak, newctx);
	return 0;
}

/* ------------------------------------------------------------------------- */
static HAK_INLINE int call_primitive (hak_t* hak, hak_ooi_t nargs)
{
	hak_oop_prim_t op_prim;

	op_prim = (hak_oop_prim_t)HAK_STACK_GETOP(hak, nargs);
	HAK_ASSERT(hak, HAK_IS_PRIM(hak, op_prim));
	HAK_ASSERT(hak, HAK_OBJ_GET_SIZE(op_prim) == HAK_PRIM_NAMED_INSTVARS);

	if (nargs < op_prim->min_nargs && nargs > op_prim->max_nargs)
	{
/* TODO: include a primitive name... */
		HAK_LOG3(hak, HAK_LOG_IC | HAK_LOG_ERROR,
			"Error - wrong number of arguments to a primitive - expecting %zd-%zd, got %zd\n",
			op_prim->min_nargs, op_prim->max_nargs, nargs);
		hak_seterrnum(hak, HAK_ECALLARG);
		return -1;
	}

	return ((hak_pfimpl_t)op_prim->impl)(hak, (hak_mod_t*)op_prim->mod, nargs);
}

/* ------------------------------------------------------------------------- */

static hak_oop_block_t find_imethod_in_class_noseterr (hak_t* hak, hak_oop_class_t _class, hak_oocs_t* name, hak_ooi_t* ivaroff, hak_oop_class_t* owner)
{
	hak_oop_t dic;

	dic = _class->mdic;
	HAK_ASSERT(hak, HAK_IS_NIL(hak, dic) || HAK_IS_DIC(hak, dic));

	if (HAK_LIKELY(!HAK_IS_NIL(hak, dic)))
	{
		hak_oop_cons_t ass;
		ass = (hak_oop_cons_t)hak_lookupdicforsymbol_noseterr(hak, (hak_oop_dic_t)dic, name);
		if (HAK_LIKELY(ass))
		{
			hak_oop_t val;
			val = HAK_CONS_CDR(ass);
			HAK_ASSERT(hak, HAK_IS_CONS(hak, val));
			if (!HAK_IS_NIL(hak, HAK_CONS_CDR(val)))
			{
				/* TODO: further check if it's a method block? */
				*owner = _class;
				*ivaroff = HAK_OOP_TO_SMOOI(_class->nivars_super);
				return (hak_oop_block_t)HAK_CONS_CDR(val); /* car - class method, cdr - instance method */
			}
		}
	}

	return HAK_NULL;
}

static hak_oop_block_t find_imethod_noseterr (hak_t* hak, hak_oop_class_t class_, hak_oop_t op_name, int to_super, hak_ooi_t* ivaroff, hak_oop_class_t* owner)
{
	hak_oocs_t name;

	HAK_ASSERT(hak, HAK_IS_CLASS(hak, class_));
	/*HAK_ASSERT(hak, HAK_IS_SYMBOL(hak, op_name));*/
	HAK_ASSERT(hak, HAK_OBJ_IS_CHAR_POINTER(op_name));

	name.ptr = HAK_OBJ_GET_CHAR_SLOT(op_name);
	name.len = HAK_OBJ_GET_SIZE(op_name);

	if (to_super)
	{
		class_ = (hak_oop_class_t)class_->superclass;
		if (!HAK_IS_CLASS(hak, class_)) return HAK_NULL;
	}

	do
	{
		hak_oop_block_t mth;
		mth = find_imethod_in_class_noseterr(hak, class_, &name, ivaroff, owner);
		if (mth) return mth;
		class_ = (hak_oop_class_t)class_->superclass;
	}
	while (HAK_IS_CLASS(hak, class_));

	return HAK_NULL;
}

static hak_oop_block_t find_cmethod_noseterr (hak_t* hak, hak_oop_class_t _class, hak_oop_t op_name, int to_super, hak_ooi_t* ivaroff, hak_oop_class_t* owner)
{
	hak_oocs_t name;
	hak_oop_class_t xclass;

/* TODO: implement method cache */
	HAK_ASSERT(hak, HAK_IS_CLASS(hak, _class));
	/*HAK_ASSERT(hak, HAK_IS_SYMBOL(hak, op_name));*/
	HAK_ASSERT(hak, HAK_OBJ_IS_CHAR_POINTER(op_name));

	name.ptr = HAK_OBJ_GET_CHAR_SLOT(op_name);
	name.len = HAK_OBJ_GET_SIZE(op_name);

	xclass = _class;
	if (to_super)
	{
		xclass = (hak_oop_class_t)xclass->superclass;
		if (!HAK_IS_CLASS(hak, xclass)) return HAK_NULL;
	}

	do
	{
		hak_oop_t dic;

		dic = xclass->mdic;
		HAK_ASSERT(hak, HAK_IS_NIL(hak, dic) || HAK_IS_DIC(hak, dic));

		if (HAK_LIKELY(!HAK_IS_NIL(hak, dic)))
		{
			hak_oop_cons_t ass;
			ass = (hak_oop_cons_t)hak_lookupdicforsymbol_noseterr(hak, (hak_oop_dic_t)dic, &name);
			if (HAK_LIKELY(ass))
			{
				hak_oop_t val;
				val = HAK_CONS_CDR(ass);
				HAK_ASSERT(hak, HAK_IS_CONS(hak, val));
				if (!HAK_IS_NIL(hak, HAK_CONS_CAR(val)))
				{
					/* TODO: further check if it's a method block? */
					*owner = xclass;
					/* ivaroff isn't useful for a class method but is useful for class instatiation method
					 * (INSTA bit on in the mask field) */
					*ivaroff = HAK_OOP_TO_SMOOI(xclass->nivars_super);
					return (hak_oop_block_t)HAK_CONS_CAR(val); /* car - class method, cdr - instance method */
				}
			}
		}
		xclass = (hak_oop_class_t)xclass->superclass;
	}
	while (HAK_IS_CLASS(hak, xclass));

	/* If the following two lines are uncommented, the class method of Class must be explicitly defined
	 *   fun Class:name() {...}
	 *   class X { }
	 * If name is defined as an instance method of Class, other classes can call 'name' as a class method.
	 *    X:name
	 * but Class itself can't call it as Class:name. This is possible only if 'fun Class::name()' is also
	 * defined.
	xclass = HAK_CLASSOF(hak, _class);
	if (xclass == _class) return HAK_NULL;
	*/

	/* find the instance method of the Class class as a class is an instance of the Class class. */
	/* TODO: may need to traverse up if Class is a subclass in some other Clss-related abstraction... */
	return find_imethod_in_class_noseterr(hak, (hak_oop_class_t)HAK_CLASSOF(hak, _class), &name, ivaroff, owner);
}

int hak_class_responds_to (hak_t* hak, hak_oop_t rcv, hak_oop_t msg)
{
	hak_oop_block_t mth_blk;
	hak_oop_class_t owner;
	hak_ooi_t ivaroff;

	HAK_ASSERT(hak, HAK_IS_CLASS(hak, rcv));
	mth_blk = find_cmethod_noseterr(hak, (hak_oop_class_t)rcv, msg, 0, &ivaroff, &owner);

	return mth_blk != HAK_NULL;
}

int hak_inst_responds_to (hak_t* hak, hak_oop_t rcv, hak_oop_t msg)
{
	hak_oop_block_t mth_blk;
	hak_oop_class_t _class, owner;
	hak_ooi_t ivaroff;

	_class = (hak_oop_class_t)HAK_CLASSOF(hak, rcv);
	HAK_ASSERT(hak, _class != HAK_NULL);
	HAK_ASSERT(hak, HAK_IS_CLASS(hak, _class));
	mth_blk = find_imethod_noseterr(hak, _class, msg, 0, &ivaroff, &owner);

	return mth_blk != HAK_NULL;
}

static HAK_INLINE int send_message (hak_t* hak, hak_oop_t rcv, hak_oop_t msg, int to_super, hak_ooi_t nargs, hak_ooi_t nrvars)
{
	hak_oop_block_t mth_blk;
	hak_oop_context_t newctx;
	hak_oop_class_t _class, owner;
	hak_ooi_t ivaroff;
	int x;

	HAK_ASSERT(hak, HAK_OBJ_IS_CHAR_POINTER(msg));
	/*HAK_ASSERT(hak, HAK_IS_SYMBOL(hak, msg));*/

/* ============================= */
/* TODO: implement methods cache */
/* ============================= */
	if (HAK_IS_CLASS(hak, rcv))
	{
		_class = (hak_oop_class_t)rcv;
		mth_blk = find_cmethod_noseterr(hak, _class, msg, to_super, &ivaroff, &owner);
		if (!mth_blk) goto msg_not_found;

		if (GET_BLK_MASK_INSTA(HAK_OOP_TO_SMOOI(mth_blk->attr_mask))) /* #ci method */
		{
			hak_oop_t newrcv;

			hak_pushvolat(hak, (hak_oop_t*)&mth_blk);
			hak_pushvolat(hak, &msg);
			hak_pushvolat(hak, &rcv);
			newrcv = hak_instantiate(hak, (hak_oop_class_t)_class, HAK_NULL, 0);
			hak_popvolats(hak, 3);
			if (HAK_UNLIKELY(!newrcv)) return -1;

			HAK_STACK_SETRCV(hak, nargs, newrcv); /* prepare_new_context() will take this as a receiver */
		}
	}
	else
	{
		/*HAK_ASSERT(hak, HAK_IS_INSTANCE(hak, rcv));*/
		_class = (hak_oop_class_t)HAK_CLASSOF(hak, rcv);
		HAK_ASSERT(hak, _class != HAK_NULL);
		HAK_ASSERT(hak, HAK_IS_CLASS(hak, _class));
		mth_blk = find_imethod_noseterr(hak, _class, msg, to_super, &ivaroff, &owner);
		if (!mth_blk)
		{
		msg_not_found:
			hak_seterrbfmt(hak, HAK_ENOENT, "'%.*js' not found in %O", HAK_OBJ_GET_SIZE(msg), HAK_OBJ_GET_CHAR_SLOT(msg), _class);
			return -1;
		}
	}

	x = __activate_block(hak, mth_blk, nargs, nrvars, 1 /* is_msgsend */, ivaroff, &newctx);
	if (HAK_UNLIKELY(x <= -1)) return -1;

	/* update the method owner field of the new context created */
	newctx->owner = (hak_oop_t)owner;

	SWITCH_ACTIVE_CONTEXT(hak, newctx);
	return 0;
}

/* ------------------------------------------------------------------------- */

static int get_ip_dbgi (hak_t* hak, hak_oop_function_t f, hak_ooi_t ip, hak_dbgi_t* dbgi)
{
	HAK_ASSERT(hak, f != HAK_NULL);
	HAK_MEMSET(dbgi, 0, HAK_SIZEOF(*dbgi));

	if (f->dbgi != hak->_nil && ip >= 0 && ip < HAK_OBJ_GET_SIZE(f->dbgi) / HAK_SIZEOF(*dbgi))
	{
		hak_dbgi_t* f_dbgi;
		f_dbgi = (hak_dbgi_t*)HAK_OBJ_GET_BYTE_SLOT(f->dbgi);

		*dbgi = f_dbgi[ip];
		if (!dbgi->fname && hak->c) dbgi->fname = hak->c->cci_arg.name;
		return 0; /* found */
	}

	return -1; /* not found */
}

/* ------------------------------------------------------------------------- */

static HAK_INLINE int do_throw (hak_t* hak, hak_oop_t val, hak_ooi_t ip)
{
	hak_oop_context_t catch_ctx;
	hak_ooi_t catch_ip, clsp, sp;
	hak_oop_context_t c;

	if (HAK_EXSTACK_IS_EMPTY(hak))
	{
		hak_dbgi_t dbgi;

		/* the exception stack is empty.
		 * clear the class stack if it is not empty */
		while (!HAK_CLSTACK_IS_EMPTY(hak)) HAK_CLSTACK_POP(hak);

		if (get_ip_dbgi(hak, hak->active_function, ip, &dbgi) <= -1)
		{
			HAK_LOG1(hak, HAK_LOG_IC | HAK_LOG_WARN,
				"Warning - exception not handled - %O", val);
			hak_seterrbfmt(hak, HAK_EEXCEPT, "exception not handled - %O", val);
		}
		else
		{
			hak_loc_t loc;
			HAK_LOG3(hak, HAK_LOG_IC | HAK_LOG_WARN,
				"Warning - exception not handled %js:%zu - %O\n",
				(dbgi.fname? dbgi.fname: oocstr_dash), dbgi.sline, val);
			HAK_MEMSET(&loc, 0, HAK_SIZEOF(loc));
			loc.file = dbgi.fname;
			loc.line = dbgi.sline;
			hak_seterrbfmtloc(hak, HAK_EEXCEPT, &loc, "exception not handled - %O", val);
			/* column number is not available */
		}

		/* output backtrace */
		HAK_LOG0(hak, HAK_LOG_IC | HAK_LOG_INFO, "[BACKTRACE]\n");
		c = hak->active_context;
		if ((hak_oop_t)c != hak->_nil && ip >= 0)
		{
			hak_oop_function_t f;
			hak_ooi_t cip;

			cip = ip; /* use the given ip for the active context instead of the value from the ip field */
			do
			{
				f = c->base;
				if (get_ip_dbgi(hak, f, cip, &dbgi) >= 0)
				{
					/* TODO: include arguments? */
					HAK_LOG7(hak, HAK_LOG_IC | HAK_LOG_INFO, "  %.*js%js%.*js(%js:%zu)\n",
						(c->owner == hak->_nil? 0: HAK_OBJ_GET_SIZE(((hak_oop_class_t)c->owner)->name)),
						(c->owner == hak->_nil? oocstr_none: ((hak_oop_char_t)((hak_oop_class_t)c->owner)->name)->slot),
						(c->owner == hak->_nil? oocstr_none: oocstr_colon),
						(c->name == hak->_nil? 0: HAK_OBJ_GET_SIZE(((hak_oop_char_t)c->name))),
						(c->name == hak->_nil? oocstr_none: ((hak_oop_char_t)c->name)->slot),
						(dbgi.fname? dbgi.fname: oocstr_dash), dbgi.sline);
				}

				c = c->sender;
				if ((hak_oop_t)c == hak->_nil) break;
				cip = HAK_OOP_TO_SMOOI(c->ip);
			} while (1);
		}

		/* exception not handled. terminate the active process */
		/*terminate_process(hak, hak->processor->active); <- the vm cleanup code will do this */
		return -1;
	}

	/* pop the exception stack to get information to rewind context */
	HAK_EXSTACK_POP_TO(hak, catch_ctx, catch_ip, clsp, sp);

	/* discard unfinished class definitions for the exception thrown.
	 *
	 * (try
	 *    (class X
	 *      (throw "exception")
	 *  catch (x)
	 *    (printf "exception %O\n" x)
	 * )
	 * 'throw' is triggered before the end of defintion of X is reached.
	 */
	HAK_CLSTACK_CHOP(hak, clsp);

	/* the below code is similar to do_return_from_block() */
	hak->ip = -1; /* mark context dead. saved into hak->active_context->ip in SWITCH_ACTIVE_CONTEXT */
	SWITCH_ACTIVE_CONTEXT(hak, catch_ctx);
	hak->ip = catch_ip; /* override the instruction pointer */

	hak->sp = sp; /* restore the stack pointer of the active process context */

	/* push the exception value to the stack */
	HAK_STACK_PUSH(hak, val);
	return 0;
}

/* ------------------------------------------------------------------------- */

static void supplement_errmsg (hak_t* hak, hak_ooi_t ip)
{
	hak_dbgi_t dbgi;

	if (get_ip_dbgi(hak, hak->active_function, ip, &dbgi) >= 0)
	{
		hak_loc_t orgloc = hak->errloc;
		const hak_ooch_t* orgmsg = hak_backuperrmsg(hak);
		hak_errnum_t orgnum = HAK_ERRNUM(hak);

		orgloc.line = dbgi.sline; /* update the line of the location at least */
		hak_seterrbfmtloc(hak, orgnum, &orgloc, "%js (%js:%zu)",
			orgmsg, (dbgi.fname? dbgi.fname: oocstr_dash), dbgi.sline);
		/* no column info available */
	}
}

static int do_throw_with_internal_errmsg (hak_t* hak, hak_ooi_t ip)
{
	hak_oop_t ex;
/* TODO: consider throwing an exception object instead of a string? */
	ex = hak_makestring(hak, hak->errmsg.buf, hak->errmsg.len); /* TODO: include error location in the message? */
	if (HAK_UNLIKELY(!ex)) return -1;
	if (do_throw(hak, ex, ip) <= -1) return -1;
	return 0;
}

/* ------------------------------------------------------------------------- */


#if defined(ENABLE_SYSCMD)
/* EXPERIMENTAL CODE INTEGRATING EXTERNAL COMMANDS */

#include <unistd.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <limits.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <stdio.h>

extern char **environ;

#define _PATH_DEFPATH "/usr/bin:/bin"

static int is_regular_executable_file_by_me(const char *path)
{
	struct stat st;
	if (stat(path, &st) == -1) return 0;
	return S_ISREG(st.st_mode) && access(path, X_OK) == 0; /* use eaccess instead?? */
}

static char* find_exec (hak_t* hak, const char *name)
{
	size_t lp, ln;
	char buf[PATH_MAX];
	const char *bp, *path, *p;

	bp = buf;

	/* Get the path we're searching. */
	if (!(path = getenv("PATH"))) path = _PATH_DEFPATH;

	ln = strlen(name);
	do
	{
		/* Find the end of this path element. */
		for (p = path; *path != 0 && *path != ':'; path++) ;

		/*
		 * It's a SHELL path -- double, leading and trailing colons
		 * mean the current directory.
		 */
		if (p == path)
		{
			p = ".";
			lp = 1;
		}
		else
		{
			lp = path - p;
		}

		/*
		 * If the path is too long complain.  This is a possible
		 * security issue; given a way to make the path too long
		 * the user may execute the wrong program.
		 */
		if (lp + ln + 2 > sizeof(buf)) continue;

		memcpy(buf, p, lp);
		buf[lp] = '/';
		memcpy(buf + lp + 1, name, ln);
		buf[lp + ln + 1] = '\0';

		if (is_regular_executable_file_by_me(bp)) return strdup(bp);

	}
	while (*path++ == ':'); /* Otherwise, *path was NUL */


done:
	hak_seterrbfmt(hak, HAK_ENOENT, "callable %hs not found", name);
	return HAK_NULL;
}


static HAK_INLINE int exec_syscmd (hak_t* hak, hak_ooi_t nargs)
{
	hak_oop_word_t op_cmd;
	hak_bch_t* cmd = HAK_NULL;
	hak_bch_t* xcmd = HAK_NULL;

	op_cmd = (hak_oop_word_t)HAK_STACK_GETOP(hak, nargs);
	/*HAK_ASSERT(hak, HAK_IS_STRING(hak, op_cmd) || HAK_IS_SYMBOL(hak, op_cmd));*/
	HAK_ASSERT(hak, HAK_OBJ_IS_CHAR_POINTER(op_cmd));

	if (HAK_OBJ_GET_SIZE(op_cmd) == 0 || hak_count_oocstr(HAK_OBJ_GET_CHAR_SLOT(op_cmd)) != HAK_OBJ_GET_SIZE(op_cmd))
	{
		/* '\0' is contained in the middle */
		hak_seterrbfmt(hak, HAK_EINVAL, "invalid callable %O", op_cmd);
		goto oops;
	}

	cmd = hak_dupootobcstr(hak, HAK_OBJ_GET_CHAR_SLOT(op_cmd), HAK_NULL);
	if (!cmd) goto oops;

	if (hak_find_bchar_in_bcstr(cmd, '/'))
	{
		if (!is_regular_executable_file_by_me(cmd))
		{
			hak_seterrbfmt(hak, HAK_ECALL, "cannot execute %O", op_cmd);
			goto oops;
		}

		xcmd = cmd;
	}
	else
	{
		xcmd = find_exec(hak, cmd);
		if (!xcmd) goto oops;
	}

{ /* TODO: make it a callback ... */
	pid_t pid;
	int status;

	pid = fork();
	if (pid == -1) goto oops;

/* TODO: set a new process group / session leader??? */

	if (pid == 0)
	{
		hak_bch_t** argv;
		hak_ooi_t i;

		/* TODO: close file descriptors??? */
		argv = (hak_bch_t**)hak_allocmem(hak, (nargs + 2) * HAK_SIZEOF(*argv));
		if (HAK_LIKELY(argv))
		{
			argv[0] = cmd;
/*HAK_DEBUG1 (hak, "NARG %d\n", (int)nargs);*/
			for (i = 0; i < nargs;)
			{
				hak_oop_t ta = HAK_STACK_GETARG(hak, nargs, i);
/* TODO: check if an argument is a string or a symbol */
				if (HAK_OOP_IS_SMOOI(ta))
				{
/* TODO: rewrite this part */
					hak_bch_t tmp[64];
					snprintf(tmp, sizeof(tmp), "%ld", (long int)HAK_OOP_TO_SMOOI(ta));
					argv[++i] = hak_dupbchars(hak, tmp, strlen(tmp));
				}
				else
				{
				#if defined(HAK_OOCH_IS_UCH)
					argv[++i] = hak_dupootobchars(hak, HAK_OBJ_GET_CHAR_SLOT(ta), HAK_OBJ_GET_SIZE(ta), HAK_NULL);
				#else
					argv[++i] = hak_dupoochars(hak, HAK_OBJ_GET_CHAR_SLOT(ta), HAK_OBJ_GET_SIZE(ta));
				#endif
				}
				/*HAK_DEBUG2 (hak, "ARG %d -> %hs\n", (int)i - 1, argv[i]);*/
			}
			argv[nargs + 1] = HAK_NULL;
			execvp(xcmd, argv);
		}

		if (cmd) hak_freemem(hak, cmd);
		if (xcmd && xcmd != cmd) hak_freemem(hak, xcmd);
		_exit (255);
	}

	waitpid (pid, &status, 0); /* TOOD: enhance this waiting */

	HAK_STACK_SETRET(hak, nargs, HAK_SMOOI_TO_OOP(WEXITSTATUS(status)));
}

	hak_freemem(hak, cmd);
	if (xcmd != cmd) hak_freemem(hak, xcmd);
	return 0;

oops:
	if (cmd) hak_freemem(hak, cmd);
	if (xcmd && xcmd != cmd) hak_freemem(hak, xcmd);
	return -1;
}

#endif

/* ------------------------------------------------------------------------- */
static hak_oop_process_t start_initial_process (hak_t* hak, hak_oop_context_t ctx)
{
	hak_oop_process_t proc;

	/* there must be no active process when this function is called */
	HAK_ASSERT(hak, hak->processor->runnable.count == HAK_SMOOI_TO_OOP(0));
	HAK_ASSERT(hak, hak->processor->active == hak->nil_process);

	proc = make_process(hak, ctx);
	if (HAK_UNLIKELY(!proc)) return HAK_NULL;

	/* skip RUNNABLE and go to RUNNING */
	chain_into_processor(hak, proc, HAK_PROCESS_STATE_RUNNING);
	hak->processor->active = proc;

	/* do something that resume_process() would do with less overhead */
	HAK_ASSERT(hak, (hak_oop_t)proc->current_context != hak->_nil);
	HAK_ASSERT(hak, proc->current_context == proc->initial_context);
	SWITCH_ACTIVE_CONTEXT(hak, proc->current_context);

	return proc;
}

static int start_initial_process_and_context (hak_t* hak, hak_ooi_t initial_ip, hak_ooi_t nlvars)
{
	hak_oop_context_t ctx;
	hak_oop_process_t proc;
	hak_ooi_t attr_mask;

	attr_mask = ENCODE_BLK_MASK(0, 0, 0, 0, nlvars);
	/* create the initial context over the initial function */
	ctx = make_context(hak, nlvars);
	if (HAK_UNLIKELY(!ctx)) return -1;

	hak->ip = initial_ip;
	hak->sp = -1;

	ctx->ip = HAK_SMOOI_TO_OOP(initial_ip);
	ctx->req_nrets = HAK_SMOOI_TO_OOP(1);
	ctx->attr_mask = HAK_SMOOI_TO_OOP(attr_mask);
	ctx->home = hak->initial_function->home; /* this should be nil */
	ctx->sender = (hak_oop_context_t)hak->_nil; /* the initial context has nil in the sender field */
	ctx->base = hak->initial_function;
	ctx->receiver = hak->_nil; /* TODO: change this? keep this in sync with the dummy receiver used in the call instruction generated for xlist */
	HAK_ASSERT(hak, (hak_oop_t)ctx->home == hak->_nil);

	/* [NOTE]
	 *  the sender field of the initial context is nil.
	 *  especially, the fact that the sender field is nil is used by
	 *  the main execution loop for breaking out of the loop */

	HAK_ASSERT(hak, hak->active_context == HAK_NULL);

	/* hak_gc() uses hak->processor when hak->active_context
	 * is not NULL. at this poinst, hak->processor should point to
	 * an instance of ProcessScheduler. */
	HAK_ASSERT(hak, (hak_oop_t)hak->processor != hak->_nil);
	HAK_ASSERT(hak, hak->processor->runnable.count == HAK_SMOOI_TO_OOP(0));

	/* start_initial_process() calls the SWITCH_ACTIVE_CONTEXT() macro.
	 * the macro assumes a non-null value in hak->active_context.
	 * let's force set active_context to ctx directly. */
	hak->active_context = ctx;

	hak_pushvolat(hak, (hak_oop_t*)&ctx);
	proc = start_initial_process(hak, ctx);
	hak_popvolat(hak);
	if (HAK_UNLIKELY(!proc)) return -1;

	/* the stack must contain nothing as it should emulate the expresssion - (the-initial-function).
	 * for a normal function call, the function object and arguments are pushed by the caller.
	 * __activate_function() creates a new context and pops the function object and arguments off the stack.
	 * at this point, it should be as if the pop-off has been completed.
	 * because this is the very beginning, nothing should exist in the stack */
	HAK_ASSERT(hak, hak->sp == -1);
	HAK_ASSERT(hak, hak->sp == HAK_OOP_TO_SMOOI(proc->sp));

	HAK_ASSERT(hak, proc == hak->processor->active);
	hak->initial_context = proc->initial_context;
	HAK_ASSERT(hak, hak->initial_context == hak->active_context);

	return 0;
}

/* ------------------------------------------------------------------------- */

static HAK_INLINE int switch_process_if_needed (hak_t* hak)
{
	if (hak->sem_heap_count > 0)
	{
		/* handle timed semaphores */
		hak_ntime_t ft, now;

		vm_gettime(hak, &now);

		do
		{
			HAK_ASSERT(hak, hak->sem_heap[0]->subtype == HAK_SMOOI_TO_OOP(HAK_SEMAPHORE_SUBTYPE_TIMED));
			HAK_ASSERT(hak, HAK_OOP_IS_SMOOI(hak->sem_heap[0]->u.timed.ftime_sec));
			HAK_ASSERT(hak, HAK_OOP_IS_SMOOI(hak->sem_heap[0]->u.timed.ftime_nsec));

			HAK_INIT_NTIME (&ft,
				HAK_OOP_TO_SMOOI(hak->sem_heap[0]->u.timed.ftime_sec),
				HAK_OOP_TO_SMOOI(hak->sem_heap[0]->u.timed.ftime_nsec)
			);

			if (HAK_CMP_NTIME(&ft, (hak_ntime_t*)&now) <= 0)
			{
				hak_oop_process_t proc;

			signal_timed:
				/* waited long enough. signal the semaphore */

				proc = signal_semaphore(hak, hak->sem_heap[0]);
				/* [NOTE] no hak_pushvolat() on proc. no GC must occur
				 *        in the following line until it's used for
				 *        wake_process() below. */
				delete_from_sem_heap(hak, 0); /* hak->sem_heap_count is decremented in delete_from_sem_heap() */

				/* if no process is waiting on the semaphore,
				 * signal_semaphore() returns hak->_nil. */

				if (hak->processor->active == hak->nil_process && (hak_oop_t)proc != hak->_nil)
				{
					/* this is the only runnable process.
					 * switch the process to the running state.
					 * it uses wake_process() instead of
					 * switch_to_process() as there is no running
					 * process at this moment */

				#if defined(HAK_DEBUG_VM_PROCESSOR) && (HAK_DEBUG_VM_PROCESSOR >= 2)
					HAK_LOG2(hak, HAK_LOG_IC | HAK_LOG_DEBUG, "Processor - switching to a process[%zd] while no process is active - total runnables %zd\n", HAK_OOP_TO_SMOOI(proc->id), HAK_OOP_TO_SMOOI(hak->processor->runnable.count));
				#endif

					HAK_ASSERT(hak, proc->state == HAK_SMOOI_TO_OOP(HAK_PROCESS_STATE_RUNNABLE));
					HAK_ASSERT(hak, proc == hak->processor->runnable.last); /* resume_process() appends to the runnable list */
				#if 0
					wake_process(hak, proc); /* switch to running */
					hak->proc_switched = 1;
				#else
					switch_to_process_from_nil(hak, proc);
				#endif
				}
			}
			else if (hak->processor->active == hak->nil_process)
			{
				/* no running process. before firing time. */
				HAK_SUB_NTIME (&ft, &ft, (hak_ntime_t*)&now);

				if (hak->sem_io_wait_count > 0)
				{
					/* no running process but io semaphore being waited on */
					vm_muxwait(hak, &ft);

					/* exit early if a process has been woken up.
					 * the break in the else part further down will get hit
					 * eventually even if the following line doesn't exist.
					 * having the following line causes to skip firing the
					 * timed semaphore that would expire between now and the
					 * moment the next inspection occurs. */
					if (hak->processor->active != hak->nil_process) goto switch_to_next;
				}
				else
				{
					int halting;

#if defined(ENABLE_GCFIN)
					/* no running process, no io semaphore */
					if ((hak_oop_t)hak->sem_gcfin != hak->_nil && hak->sem_gcfin_sigreq) goto signal_sem_gcfin;
#endif
					halting = vm_sleep(hak, &ft);

					if (halting)
					{
						vm_gettime(hak, &now);
						goto signal_timed;
					}
				}
				vm_gettime(hak, &now);
			}
			else
			{
				/* there is a running process. go on */
				break;
			}
		}
		while (hak->sem_heap_count > 0 && !hak->abort_req);
	}

	if (hak->sem_io_wait_count > 0)
	{
		if (hak->processor->active == hak->nil_process)
		{
			hak_ntime_t ft;

			HAK_ASSERT(hak, hak->processor->runnable.count == HAK_SMOOI_TO_OOP(0));
			/* no running process while there is an io semaphore being waited for */

#if defined(ENABLE_GCFIN)
			if ((hak_oop_t)hak->sem_gcfin != hak->_nil && hak->sem_gcfin_sigreq) goto signal_sem_gcfin;
#endif

			if (hak->processor->suspended.count == HAK_SMOOI_TO_OOP(0))
			{
				/* no suspended process. the program is buggy or is probably being terminated forcibly.
				 * the default signal handler may lead to this situation. */
				hak->abort_req = 1;
			}
			else
			{
				do
				{
					HAK_INIT_NTIME (&ft, 3, 0); /* TODO: use a configured time */
					vm_muxwait(hak, &ft);
				}
				while (hak->processor->active == hak->nil_process && !hak->abort_req);
			}
		}
		else
		{
			/* well, there is a process waiting on one or more semaphores while
			 * there are other normal processes to run. check IO activities
			 * before proceeding to handle normal process scheduling */

			/* [NOTE] the check with the multiplexer may happen too frequently
			 *       because this is called everytime process switching is requested.
			 *       the actual callback implementation should try to avoid invoking
			 *       actual system calls too frequently for less overhead. */
			vm_muxwait(hak, HAK_NULL);
		}
	}

#if defined(ENABLE_GCFIN)
	if ((hak_oop_t)hak->sem_gcfin != hak->_nil)
	{
		hak_oop_process_t proc;

		if (hak->sem_gcfin_sigreq)
		{
		signal_sem_gcfin:
			HAK_LOG0 (hak, HAK_LOG_IC | HAK_LOG_DEBUG, "Signaled GCFIN semaphore\n");
			proc = signal_semaphore(hak, hak->sem_gcfin);

			if (hak->processor->active == hak->nil_process && (hak_oop_t)proc != hak->_nil)
			{
				HAK_ASSERT(hak, proc->state == HAK_SMOOI_TO_OOP(HAK_PROCESS_STATE_RUNNABLE));
				HAK_ASSERT(hak, proc == hak->processor->runnable.first);
				switch_to_process_from_nil(hak, proc);
			}

			hak->sem_gcfin_sigreq = 0;
		}
		else
		{
			/* the gcfin semaphore signalling is not requested and there are
			 * no runnable processes nor no waiting semaphores. if there is
			 * process waiting on the gcfin semaphore, i will just schedule
			 * it to run by calling signal_semaphore() on hak->sem_gcfin.
			 */
			/* TODO: check if this is the best implementation practice */
			if (hak->processor->active == hak->nil_process)
			{
				/* there is no active process. in most cases, the only process left
				 * should be the gc finalizer process started in the System>>startup.
				 * if there are other suspended processes at this point, the processes
				 * are not likely to run again.
				 *
				 * imagine the following single line program that creates a process
				 * but never start it.
				 *
				 *    method(#class) main { | p |  p := [] newProcess. }
				 *
				 * the gc finalizer process and the process assigned to p exist.
				 * when the code reaches here, the 'p' process still is alive
				 * despite no active process nor no process waiting on timers
				 * and semaphores. so when the entire program terminates, there
				 * might still be some suspended processes that are not possible
				 * to schedule.
				 */

				HAK_LOG4(hak, HAK_LOG_IC | HAK_LOG_DEBUG,
					"Signaled GCFIN semaphore without gcfin signal request - total %zd runnable/running %zd suspended %zd - sem_io_wait_count %zu\n",
					HAK_OOP_TO_SMOOI(hak->processor->total_count),
					HAK_OOP_TO_SMOOI(hak->processor->runnable.count),
					HAK_OOP_TO_SMOOI(hak->processor->suspended.count),
					hak->sem_io_wait_count);
				proc = signal_semaphore(hak, hak->sem_gcfin);
				if ((hak_oop_t)proc != hak->_nil)
				{
					HAK_ASSERT(hak, proc->state == HAK_SMOOI_TO_OOP(HAK_PROCESS_STATE_RUNNABLE));
					HAK_ASSERT(hak, proc == hak->processor->runnable.first);
					hak->_system->cvar[2] = hak->_true; /* set gcfin_should_exit in System to true. if the postion of the class variable changes, the index must get changed, too. */
					switch_to_process_from_nil(hak, proc); /* sechedule the gc finalizer process */
				}
			}
		}
	}
#endif


#if 0
	while (hak->sem_list_count > 0)
	{
		/* handle async signals */
		--hak->sem_list_count;
		signal_semaphore(hak, hak->sem_list[hak->sem_list_count]);
		if (hak->processor->active == hak->nil_process)
		{suspended process
		}
	}
	/*
	if (semaphore heap has pending request)
	{
		signal them...
	}*/
#endif

	if (hak->processor->active == hak->nil_process)
	{
		/* no more waiting semaphore and no more process */
		HAK_ASSERT(hak, hak->processor->runnable.count = HAK_SMOOI_TO_OOP(0));
		HAK_LOG0 (hak, HAK_LOG_IC | HAK_LOG_DEBUG, "No more runnable process\n");

		if (HAK_OOP_TO_SMOOI(hak->processor->suspended.count) > 0)
		{
			/* there exist suspended processes while no processes are runnable.
			 * most likely, the running program contains process/semaphore related bugs */
			HAK_LOG1(hak, HAK_LOG_IC | HAK_LOG_WARN,
				"Warning - %zd suspended process(es) found in process switcher - check your program\n",
				HAK_OOP_TO_SMOOI(hak->processor->suspended.count));
		}
		return 0;
	}

switch_to_next:
	/* TODO: implement different process switching scheme - time-slice or clock based??? */
#if defined(HAK_EXTERNAL_PROCESS_SWITCH)
	if (hak->switch_proc)
	{
#endif
		if (!hak->proc_switched)
		{
			switch_to_next_runnable_process(hak);
			hak->proc_switched = 0;
		}
#if defined(HAK_EXTERNAL_PROCESS_SWITCH)
		hak->switch_proc = 0;
	}
	else hak->proc_switched = 0;
#endif

	return 1;
}

/* ------------------------------------------------------------------------- */

static HAK_INLINE int do_return_from_block (hak_t* hak)
{
	/*if (hak->active_context == hak->processor->active->initial_context)*/
	if ((hak_oop_t)hak->active_context->home == hak->_nil)
	{
		/* the active context to return from is an initial context of
		 * the active process. let's terminate the process.
		 * the initial context has been forged over the initial function
		 * in start_initial_process_and_context() */
		HAK_ASSERT(hak, (hak_oop_t)hak->active_context->sender == hak->_nil);
		hak->active_context->ip = HAK_SMOOI_TO_OOP(-1); /* mark context dead */
		terminate_process(hak, hak->processor->active);
		return 1; /* indiate process termination */
	}
	else
	{
		/* The compiler produces the class_exit instruction and the try_exit instruction
		 * for return, break, continue in a class defintion scope and in a try-catch scope
		 * respectively.

		[CASE 1]
		(class X
			; ....
			(return 20) ;  the class defintion isn't over, but return is executed,
			; ....
		)

		[CASE 2]
		(try
			(class C
				(return 200)
				(printf "============================\n"))
		catch (e)
			(printf "EXCEPTION => %O\n" e)
		)

		[CASE 3]
		(class C
			(try
			    (return 99)
			catch (e)
			    (printf "EXCEPTOIN => %O\n" e)
			)
			(printf "============================\n")
		)

		[CASE 4]
		(try
			(class C
				(try
				    (return 99)
				catch (e)
				    (printf "EXCEPTOIN => %O\n" e)
				)
				(printf "============================\n")
			)
		catch (e)
			(printf "EXCEPTOIN => %O\n" e)
		)

		[CASE 5]
		(try
			(class D
				(class C
					(try
					    (return 99)
					catch (e)
					    (printf "EXCEPTOIN => %O\n" e)
					)
					(printf "============================\n")
				)
			}
		catch (e)
			(printf "EXCEPTOIN => %O\n" e)
		)

		 * the actual return instruction handler doesn't need to care about the
		 * class stack and exception stack.
		 */

		HAK_ASSERT(hak, (hak_oop_t)hak->active_context->sender != hak->_nil);
		if (HAK_UNLIKELY(hak->active_context->sender->ip == HAK_SMOOI_TO_OOP(-1)))
		{
			HAK_LOG0 (hak, HAK_LOG_IC | HAK_LOG_ERROR, "Error - cannot return to dead context\n");
			hak_seterrbfmt(hak, HAK_EINTERN, "unable to return to dead context"); /* TODO: can i make this error catchable at the hak level? */
			return -1;
		}

		/* it is a normal block return as the active block context
		 * is not the initial context of a process */
		hak->ip = -1; /* mark context dead. saved into hak->active_context->ip in SWITCH_ACTIVE_CONTEXT */
		SWITCH_ACTIVE_CONTEXT(hak, (hak_oop_context_t)hak->active_context->sender);
		return 0; /* normal return */
	}
}

static HAK_INLINE int do_return_from_home (hak_t* hak, hak_oop_t return_value, hak_ooi_t ip)
{
#if 0
	/* if (hak->active_context == hak->processor->active->initial_context) // read the interactive mode note below... */
	if ((hak_oop_t)hak->active_context->home == hak->_nil)
	{
		/* returning from the intial context.
		 *  (return-from-home 999)
		 * the return-from-home is executed in the initial context */
		HAK_ASSERT(hak, (hak_oop_t)hak->active_context->sender == hak->_nil);
		hak->active_context->ip = HAK_SMOOI_TO_OOP(-1); /* mark the active context dead */

		if (hak->sp >= 0)
		{
			/* return-from-home has been called from where it shouldn't be. for instance,
			 *  (printf "xxx %d\n" (return-from-home 999))
			 *  -----------------------------------------------
			 *  (if (>  19 (return-from-home 20)) 30) */
			HAK_LOG1(hak, HAK_LOG_IC | HAK_LOG_WARN, "Warning - stack not empty on return-from-home - SP %zd\n", hak->sp); /* TODO: include line number and file name */
		}

		terminate_process(hak, hak->processor->active);
	}
	/*else if (hak->active_context->home == hak->processor->active->initial_context) // read the interactive mode note below...*/
	else if ((hak_oop_t)hak->active_context->home->home == hak->_nil)
	{
		/* non-local return out of the initial context
		 *  (defun y(x) (return-from-home (* x x)))
		 *  (y 999) */

		/* [NOTE]
		 * in the interactive mode, a new initial context/function/process is created
		 * for each expression (as implemented bin/main.c)
		 * hak->active_context may be the intial context of the previous expression.
		 *   (defun y(x) (return-from-home (* x x))) <-- initial context
		 *   (y 999) <- another initial context
		 * when y is called from the second initial context, the home context to return
		 * from the the first initial context. comparing hak->active_context->home against
		 * hak->initial_context doesn't return true in this case.
		 */

		HAK_ASSERT(hak, (hak_oop_t)hak->active_context->home->sender == hak->_nil);
		hak->active_context->home->ip = HAK_SMOOI_TO_OOP(-1); /* mark that the home context has returned */

		if (hak->sp >= 0)
		{
			/* return-from-home has been called from where it shouldn't be
			 *  (defun y(x) (return-from-home (* x x)))
			 *  (printf "xxx %d\n" (y 999)) */
			HAK_LOG1(hak, HAK_LOG_IC | HAK_LOG_WARN, "Warning - stack not empty on non-local return-from-home - SP %zd\n", hak->sp); /* TODO: include line number and file name */
		}

		terminate_process(hak, hak->processor->active);
	}
	else
	{
		/*
		(defun f(x)
			(defun y(x) (return-from-home (* x  x)))
			(y x)
			(printf "this line must not be printed\n");
		)
		(printf "%d\n" (f 90)) ; this should print 8100.
		(y 10); this ends up with the "unable to return from dead context" error.
		*/
		HAK_ASSERT(hak, hak->active_context != hak->processor->active->initial_context);
		HAK_ASSERT(hak, (hak_oop_t)hak->active_context->home->sender != hak->_nil);

		if (hak->active_context->home->ip == HAK_SMOOI_TO_OOP(-1))
		{
			HAK_LOG0 (hak, HAK_LOG_IC | HAK_LOG_ERROR, "Error - cannot return from dead context\n");
			hak_seterrbfmt(hak, HAK_EINTERN, "unable to return from dead context"); /* TODO: can i make this error catchable at the hak level? */
			return -1;
		}

		hak->active_context->home->ip = HAK_SMOOI_TO_OOP(-1); /* mark that the home context has returned */
		hak->ip = -1; /* mark that the active context has returned. committed to hak->active_context->ip in SWITCH_ACTIVE_CONTEXT() */
		SWITCH_ACTIVE_CONTEXT(hak, hak->active_context->home->sender);

		/* push the return value to the stack of the final active context */
		HAK_STACK_PUSH(hak, return_value);

	#if 0
		/* stack dump */
		HAK_DEBUG1 (hak, "****** non local returning %O\n", return_value);
		{
			int i;
			for (i = hak->sp; i >= 0; i--)
			{
				HAK_DEBUG2 (hak, "STACK[%d] => %O\n", i, HAK_STACK_GET(hak, i));
			}
		}
	#endif
	}

#else

	/* this part implements the non-local return by traversing the call chain
	 * until the sender of the home context is reached.
	 * it is slower than immediat return from the home context but detetts
	 * dead context better */

	if ((hak_oop_t)hak->active_context->home == hak->_nil)
	{
		/* non-local return from the intial context.
		 *  (return-from-home 999)
		 */

		/* the current active context must be the initial context of the active process */
		HAK_ASSERT(hak, hak->active_context == hak->processor->active->initial_context);
		HAK_ASSERT(hak, (hak_oop_t)hak->active_context->sender == hak->_nil);

		hak->active_context->ip = HAK_SMOOI_TO_OOP(-1); /* mark the active context dead */

	term_proc:
		if (hak->sp >= 0)
		{
			/* return-from-home has been called from where it shouldn't be. for instance,
			 *  (printf "xxx %d\n" (return-from-home 999))
			 *  -----------------------------------------------
			 *  (if (>  19 (return-from-home 20)) 30) */
			HAK_LOG1(hak, HAK_LOG_IC | HAK_LOG_WARN, "Warning - stack not empty on return-from-home - SP %zd\n", hak->sp); /* TODO: include line number and file name */
		}

		/* as the process is terminated here, the nonempty stack or not invalidating the
		 * intermediates contexts deson't really matter. */
		terminate_process(hak, hak->processor->active);
	}
	else
	{
		hak_oop_context_t sender, home, ctx;
		hak_dbgi_t dbgi;

		home = hak->active_context->home;
		sender = hak->active_context->home->sender;

		/* check if the home context is in the current call chain */
		ctx = hak->active_context;
		while ((hak_oop_t)ctx != hak->_nil)
		{
			ctx = ctx->sender;
			if (ctx == home) goto do_return;
		}

		if (get_ip_dbgi(hak, hak->active_function, ip, &dbgi) <= -1)
		{
			HAK_LOG0 (hak, HAK_LOG_IC | HAK_LOG_ERROR, "Error - cannot return from dead context - throwing an exception\n");
		}
		else
		{
			HAK_LOG2(hak, HAK_LOG_IC | HAK_LOG_ERROR, "Error - cannot return from dead context - throwing an exception (%js:%zu)\n", (dbgi.fname? dbgi.fname: oocstr_dash), dbgi.sline);
		}

		hak_seterrbfmt(hak, HAK_EINTERN, "unable to return from dead context"); /* TODO: can i make this error catchable at the hak level? */
		return do_throw_with_internal_errmsg(hak, ip);

	do_return:
		while (hak->active_context != home)
		{
			hak->ip = -1; /* mark context dead. saved into hak->active_context->ip in SWITCH_ACTIVE_CONTEXT */
			SWITCH_ACTIVE_CONTEXT(hak, (hak_oop_context_t)hak->active_context->sender);
		}

		if (HAK_UNLIKELY((hak_oop_t)sender == hak->_nil))
		{
			/* non-local return out of the initial context
			 *   (defun y(x) (return-from-home (* x x)))
			 *   (y 999)
			 * when y is activated, y's home context is itself. but the
			 *
			 * [NOTE]
			 * in the interactive mode, a new initial context/function/process is created
			 * for each expression (as implemented bin/main.c)
			 * hak->active_context may be the intial context of the previous expression.
			 *   (defun y(x) (return-from-home (* x x))) <-- initial context
			 *   (y 999) <- another initial context
			 * when y is called from the second initial context, the home context to return
			 * from the the first initial context. comparing hak->active_context->home against
			 * hak->initial_context doesn't return true in this case. */
			HAK_ASSERT(hak, (hak_oop_t)home->home == hak->_nil);
			HAK_ASSERT(hak, (hak_oop_t)hak->active_context->sender == hak->_nil);

			home->ip = HAK_SMOOI_TO_OOP(-1); /* mark the home context dead */
			goto term_proc;
		}

		HAK_ASSERT(hak, hak->active_context->sender == sender);
		hak->ip = -1; /* mark context dead. saved into hak->active_context->ip in SWITCH_ACTIVE_CONTEXT */
		SWITCH_ACTIVE_CONTEXT(hak, (hak_oop_context_t)hak->active_context->sender);
		HAK_STACK_PUSH(hak, return_value);
	}
#endif

	return 0;
}

/* ------------------------------------------------------------------------- */

static void xma_dumper (void* ctx, const char* fmt, ...)
{
	va_list ap;
	va_start (ap, fmt);
	hak_logbfmtv ((hak_t*)ctx, HAK_LOG_IC | HAK_LOG_INFO, fmt, ap);
	va_end (ap);
}

static hak_oop_t fetch_numeric_rcv_slot (hak_t* hak, hak_oop_t rcv, hak_oow_t b1)
{
	hak_oow_t w;
	hak_obj_type_t rcv_type;

	rcv_type = (hak_obj_type_t)HAK_OBJ_GET_FLAGS_TYPE(rcv);
	switch (rcv_type)
	{
		case HAK_OBJ_TYPE_CHAR:
			w = ((hak_oop_char_t)rcv)->slot[b1];
			return HAK_CHAR_TO_OOP(w);

		case HAK_OBJ_TYPE_BYTE:
			w = ((hak_oop_byte_t)rcv)->slot[b1];
			return HAK_SMOOI_TO_OOP(w);

		case HAK_OBJ_TYPE_HALFWORD:
			w = ((hak_oop_halfword_t)rcv)->slot[b1];
			return HAK_SMOOI_TO_OOP(w);

		case HAK_OBJ_TYPE_WORD:
			w = ((hak_oop_word_t)rcv)->slot[b1];
			return hak_oowtoint(hak, w);

		default:
			hak_seterrbfmt(hak, HAK_EINTERN, "internal error - invalid receiver type in fetching numeric slot value - %d", rcv_type);
			return HAK_NULL;
	}
}

static int store_into_numeric_rcv_slot (hak_t* hak, hak_oop_t rcv, hak_oow_t b1, hak_oop_t v)
{
	hak_oow_t w;
	hak_obj_type_t rcv_type;

	if (HAK_OOP_IS_CHAR(v)) w = HAK_OOP_TO_CHAR(v);
	else if (hak_inttooow(hak, v, &w) <= 0) return -1;

	rcv_type = (hak_obj_type_t)HAK_OBJ_GET_FLAGS_TYPE(rcv);
	switch (rcv_type)
	{
		case HAK_OBJ_TYPE_CHAR:
			((hak_oop_char_t)rcv)->slot[b1] = (hak_ooch_t)(hak_oochu_t)w;
			break;

		case HAK_OBJ_TYPE_BYTE:
			((hak_oop_byte_t)rcv)->slot[b1] = (hak_oob_t)w;
			break;

		case HAK_OBJ_TYPE_HALFWORD:
			((hak_oop_halfword_t)rcv)->slot[b1] = (hak_oohw_t)w;
			break;

		case HAK_OBJ_TYPE_WORD:
			((hak_oop_word_t)rcv)->slot[b1] = w;
			break;

		default:
			hak_seterrbfmt(hak, HAK_EINTERN, "internal error - invalid receiver type in storing in numeric slot - %d", rcv_type);
			return -1;
	}

	return 0;
}

static int execute (hak_t* hak)
{
	hak_oob_t bcode;
	hak_oow_t b1, b2;
	hak_oop_t return_value;
	hak_ooi_t fetched_instruction_pointer;

#if defined(HAK_PROFILE_VM)
	hak_uintmax_t inst_counter = 0;
#endif

	HAK_ASSERT(hak, hak->active_context != HAK_NULL);

	hak->abort_req = 0;
	if (vm_startup(hak) <= -1) return -1;
	hak->proc_switched = 0;

	hak->gci.lazy_sweep = 1; /* TODO: make it configurable?? */
	HAK_INIT_NTIME (&hak->gci.stat.alloc, 0, 0);
	HAK_INIT_NTIME (&hak->gci.stat.mark, 0, 0);
	HAK_INIT_NTIME (&hak->gci.stat.sweep, 0, 0);

	fetched_instruction_pointer = -1;

	while (1)
	{
		/* stop requested or no more runnable process */
		if (hak->abort_req < 0) goto oops;
		if (hak->abort_req > 0 || (!hak->no_proc_switch && switch_process_if_needed(hak) == 0)) break;

		if (HAK_UNLIKELY(hak->ip < 0 || hak->ip >= HAK_FUNCTION_GET_CODE_SIZE(hak->active_function)))
		{
			if (hak->ip < 0)
			{
				/* do_return_from_home() implements a simple check against a dead context.
				 * but the check is far from perfect. there are many ways to return from an
				 * active context and enter a dead context thereafter.
					(defun t(f)
						(set q (fun()
							(printf "hello word\n")
							(return-from-home 200)
						))
						(f)
					)
					(defun x()
						(t (fun() (return-from-home 100)))
						(printf ">>>>>>>>>>>>>>>>>>>>>>>>\n");
					)
					(x) ; x is exited by (return-from-home 100) triggered by (f)
					(printf "------------------------\n")
					(q) ; (return-from-home 200) exits t and since t is called from x, it flows back to the dead x.
				 */
				HAK_DEBUG1 (hak, "Stopping execution as a dead context gets active  - IP %zd\n", hak->ip);
			}
			else
			{
				HAK_DEBUG2 (hak, "Stopping execution as IP reached the end of bytecode(%zu) - IP %zd\n", hak->code.bc.len, hak->ip);
			}
			return_value = hak->_nil;
			goto handle_return;
		}

		fetched_instruction_pointer = hak->ip;
		FETCH_BYTE_CODE_TO(hak, bcode);
		/*while (bcode == HAK_CODE_NOOP) FETCH_BYTE_CODE_TO(hak, bcode);*/

		if (hak->vm_checkbc_cb_count) vm_checkbc(hak, bcode);

		if (HAK_UNLIKELY(hak->abort_req))
		{
			/* i place this abortion check after vm_checkbc()
			 * to honor hak_abort() if called in the callback, */
			HAK_DEBUG0 (hak, "Stopping execution for abortion request\n");
			return_value = hak->_nil;
			goto handle_return;
		}

	#if defined(HAK_PROFILE_VM)
		inst_counter++;
	#endif

		switch (bcode)
		{
			/* -------------------------------------------------------- */
			case HAK_CODE_PLUS:
			{
				/* TODO: support other binary arithmetic operators */
				hak_oop_t x1, x2, x3;
				LOG_INST_0(hak, "plus");
				x2 = HAK_STACK_GETTOP(hak); HAK_STACK_POP(hak);
				x1 = HAK_STACK_GETTOP(hak); HAK_STACK_POP(hak);
				x3 = hak_addnums(hak, x1, x2);
				if (HAK_UNLIKELY(!x3))
				{
					if (do_throw_with_internal_errmsg(hak, fetched_instruction_pointer) >= 0) break;
					goto oops_with_errmsg_supplement;
				}
				HAK_STACK_PUSH(hak, x3);
				break;
			}

			/* ------------------------------------------------- */
			case HAK_CODE_PUSH_IVAR_X:
				FETCH_PARAM_CODE_TO(hak, b1);
				goto push_ivar;
			case HAK_CODE_PUSH_IVAR_0:
			case HAK_CODE_PUSH_IVAR_1:
			case HAK_CODE_PUSH_IVAR_2:
			case HAK_CODE_PUSH_IVAR_3:
			case HAK_CODE_PUSH_IVAR_4:
			case HAK_CODE_PUSH_IVAR_5:
			case HAK_CODE_PUSH_IVAR_6:
			case HAK_CODE_PUSH_IVAR_7:
			{
				hak_oop_t rcv;

				b1 = bcode & 0x7; /* low 3 bits */
			push_ivar:
				LOG_INST_2(hak, "push_ivar %zu ## [%zd]", b1, HAK_OOP_TO_SMOOI(hak->active_context/*->mthhome*/->ivaroff));
				b1 += HAK_OOP_TO_SMOOI(hak->active_context/*->mthhome*/->ivaroff);
				rcv = hak->active_context->receiver;
				/*HAK_ASSERT(hak, HAK_OBJ_GET_FLAGS_TYPE(rcv) == HAK_OBJ_TYPE_OOP);*/
				if (HAK_LIKELY(HAK_OBJ_GET_FLAGS_TYPE(rcv) == HAK_OBJ_TYPE_OOP))
				{
					HAK_STACK_PUSH(hak, ((hak_oop_oop_t)rcv)->slot[b1]);
				}
				else
				{
					hak_oop_t v;

					v = fetch_numeric_rcv_slot(hak, rcv, b1);

					if (HAK_UNLIKELY(!v))
					{
						if (do_throw_with_internal_errmsg(hak, fetched_instruction_pointer) >= 0) break;
						goto oops_with_errmsg_supplement;
					}

					HAK_STACK_PUSH(hak, v);
				}
				break;
			}

			/* ------------------------------------------------- */

			case HAK_CODE_STORE_INTO_IVAR_X:
				FETCH_PARAM_CODE_TO(hak, b1);
				goto store_instvar;
			case HAK_CODE_STORE_INTO_IVAR_0:
			case HAK_CODE_STORE_INTO_IVAR_1:
			case HAK_CODE_STORE_INTO_IVAR_2:
			case HAK_CODE_STORE_INTO_IVAR_3:
			case HAK_CODE_STORE_INTO_IVAR_4:
			case HAK_CODE_STORE_INTO_IVAR_5:
			case HAK_CODE_STORE_INTO_IVAR_6:
			case HAK_CODE_STORE_INTO_IVAR_7:
			{
				hak_oop_t rcv, top;

				b1 = bcode & 0x7; /* low 3 bits */
			store_instvar:
				LOG_INST_2(hak, "store_into_ivar %zu ## [%zd]", b1, HAK_OOP_TO_SMOOI(hak->active_context/*->mthhome*/->ivaroff));
				b1 += HAK_OOP_TO_SMOOI(hak->active_context/*->mthhome*/->ivaroff);
				rcv = hak->active_context->receiver;
				top = HAK_STACK_GETTOP(hak);
				/*HAK_ASSERT(hak, HAK_OBJ_GET_FLAGS_TYPE(rcv) == HAK_OBJ_TYPE_OOP);*/
				if (HAK_LIKELY(HAK_OBJ_GET_FLAGS_TYPE(rcv) == HAK_OBJ_TYPE_OOP))
				{
					((hak_oop_oop_t)rcv)->slot[b1] = top;
				}
				else
				{
					if (HAK_UNLIKELY(store_into_numeric_rcv_slot(hak, rcv, b1, top) <= -1))
					{
						if (do_throw_with_internal_errmsg(hak, fetched_instruction_pointer) >= 0) break;
						goto oops_with_errmsg_supplement;
					}
				}
				break;
			}

			/* ------------------------------------------------- */
			case HAK_CODE_POP_INTO_IVAR_X:
				FETCH_PARAM_CODE_TO(hak, b1);
				goto pop_into_ivar;
			case HAK_CODE_POP_INTO_IVAR_0:
			case HAK_CODE_POP_INTO_IVAR_1:
			case HAK_CODE_POP_INTO_IVAR_2:
			case HAK_CODE_POP_INTO_IVAR_3:
			case HAK_CODE_POP_INTO_IVAR_4:
			case HAK_CODE_POP_INTO_IVAR_5:
			case HAK_CODE_POP_INTO_IVAR_6:
			case HAK_CODE_POP_INTO_IVAR_7:
			{
				hak_oop_t rcv, top;

				b1 = bcode & 0x7; /* low 3 bits */
			pop_into_ivar:
				LOG_INST_2(hak, "pop_into_ivar %zu ## [%zd]", b1, HAK_OOP_TO_SMOOI(hak->active_context->home->ivaroff));
				b1 += HAK_OOP_TO_SMOOI(hak->active_context->home->ivaroff);
				rcv = hak->active_context->receiver;
				top = HAK_STACK_GETTOP(hak);
				/*HAK_ASSERT(hak, HAK_OBJ_GET_FLAGS_TYPE(rcv) == HAK_OBJ_TYPE_OOP);*/
				if (HAK_LIKELY(HAK_OBJ_GET_FLAGS_TYPE(rcv) == HAK_OBJ_TYPE_OOP))
				{
					((hak_oop_oop_t)rcv)->slot[b1] = top;
				}
				else
				{
					if (HAK_UNLIKELY(store_into_numeric_rcv_slot(hak, rcv, b1, top) <= -1))
					{
						if (do_throw_with_internal_errmsg(hak, fetched_instruction_pointer) >= 0) break;
						goto oops_with_errmsg_supplement;
					}
				}
				HAK_STACK_POP(hak);
				break;
			}

			/* ------------------------------------------------- */
		#if 0
			/* the compiler never emits these instructions. reuse these instructions for other purposes */
			case HAK_CODE_PUSH_TEMPVAR_X:
			case HAK_CODE_STORE_INTO_TEMPVAR_X:
			case HAK_CODE_POP_INTO_TEMPVAR_X:
				FETCH_PARAM_CODE_TO(hak, b1);
				goto handle_tempvar;

			case HAK_CODE_PUSH_TEMPVAR_0:
			case HAK_CODE_PUSH_TEMPVAR_1:
			case HAK_CODE_PUSH_TEMPVAR_2:
			case HAK_CODE_PUSH_TEMPVAR_3:
			case HAK_CODE_PUSH_TEMPVAR_4:
			case HAK_CODE_PUSH_TEMPVAR_5:
			case HAK_CODE_PUSH_TEMPVAR_6:
			case HAK_CODE_PUSH_TEMPVAR_7:
			case HAK_CODE_STORE_INTO_TEMPVAR_0:
			case HAK_CODE_STORE_INTO_TEMPVAR_1:
			case HAK_CODE_STORE_INTO_TEMPVAR_2:
			case HAK_CODE_STORE_INTO_TEMPVAR_3:
			case HAK_CODE_STORE_INTO_TEMPVAR_4:
			case HAK_CODE_STORE_INTO_TEMPVAR_5:
			case HAK_CODE_STORE_INTO_TEMPVAR_6:
			case HAK_CODE_STORE_INTO_TEMPVAR_7:
			case HAK_CODE_POP_INTO_TEMPVAR_0:
			case HAK_CODE_POP_INTO_TEMPVAR_1:
			case HAK_CODE_POP_INTO_TEMPVAR_2:
			case HAK_CODE_POP_INTO_TEMPVAR_3:
			case HAK_CODE_POP_INTO_TEMPVAR_4:
			case HAK_CODE_POP_INTO_TEMPVAR_5:
			case HAK_CODE_POP_INTO_TEMPVAR_6:
			case HAK_CODE_POP_INTO_TEMPVAR_7:
			{
				hak_oop_context_t ctx;
				hak_ooi_t bx;

				b1 = bcode & 0x7; /* low 3 bits */
			handle_tempvar:

				/* when CTXTEMPVAR instructions are used, the above
				 * instructions are used only for temporary access
				 * outside a block. i can assume that the temporary
				 * variable index is pointing to one of temporaries
				 * in the relevant method context */
				ctx = hak->active_context->origin;
				bx = b1;
				HAK_ASSERT(hak, HAK_IS_CONTEXT(hak, ctx));

				if ((bcode >> 4) & 1)
				{
					/* push - bit 4 on */
					LOG_INST_1(hak, "push_tempvar %zu", b1);
					HAK_STACK_PUSH(hak, ctx->slot[bx]);
				}
				else
				{
					/* store or pop - bit 5 off */
					ctx->slot[bx] = HAK_STACK_GETTOP(hak);

					if ((bcode >> 3) & 1)
					{
						/* pop - bit 3 on */
						LOG_INST_1(hak, "pop_into_tempvar %zu", b1);
						HAK_STACK_POP(hak);
					}
					else
					{
						LOG_INST_1(hak, "store_into_tempvar %zu", b1);
					}
				}

				break;
			}
		#endif

			/* ------------------------------------------------- */
			case HAK_CODE_PUSH_LITERAL_X2:
				FETCH_PARAM_CODE_TO(hak, b1);
				FETCH_PARAM_CODE_TO(hak, b2);
				b1 = (b1 << (8 * HAK_CODE_LONG_PARAM_SIZE)) | b2;
				goto push_literal;

			case HAK_CODE_PUSH_LITERAL_X:
				FETCH_PARAM_CODE_TO(hak, b1);
				goto push_literal;

			case HAK_CODE_PUSH_LITERAL_0:
			case HAK_CODE_PUSH_LITERAL_1:
			case HAK_CODE_PUSH_LITERAL_2:
			case HAK_CODE_PUSH_LITERAL_3:
			case HAK_CODE_PUSH_LITERAL_4:
			case HAK_CODE_PUSH_LITERAL_5:
			case HAK_CODE_PUSH_LITERAL_6:
			case HAK_CODE_PUSH_LITERAL_7:
				b1 = bcode & 0x7; /* low 3 bits */
			push_literal:
				LOG_INST_1(hak, "push_literal @%zu", b1);
				/*HAK_STACK_PUSH(hak, hak->code.lit.arr->slot[b1]);*/
				HAK_STACK_PUSH(hak, hak->active_function->literal_frame[b1]);
				break;

			/* ------------------------------------------------- */
			case HAK_CODE_PUSH_OBJECT_X:
			case HAK_CODE_STORE_INTO_OBJECT_X:
			case HAK_CODE_POP_INTO_OBJECT_X:
				FETCH_PARAM_CODE_TO(hak, b1);
				goto handle_object;

			case HAK_CODE_PUSH_OBJECT_0:
			case HAK_CODE_PUSH_OBJECT_1:
			case HAK_CODE_PUSH_OBJECT_2:
			case HAK_CODE_PUSH_OBJECT_3:
			case HAK_CODE_STORE_INTO_OBJECT_0:
			case HAK_CODE_STORE_INTO_OBJECT_1:
			case HAK_CODE_STORE_INTO_OBJECT_2:
			case HAK_CODE_STORE_INTO_OBJECT_3:
			case HAK_CODE_POP_INTO_OBJECT_0:
			case HAK_CODE_POP_INTO_OBJECT_1:
			case HAK_CODE_POP_INTO_OBJECT_2:
			case HAK_CODE_POP_INTO_OBJECT_3:
			{
				hak_oop_cons_t ass;

				b1 = bcode & 0x3; /* low 2 bits */
			handle_object:
				/*ass = hak->code.lit.arr->slot[b1];*/
				ass = (hak_oop_cons_t)hak->active_function->literal_frame[b1];
				HAK_ASSERT(hak, HAK_IS_CONS(hak, ass));
				/* this association is an entry in the system dictionary.
				 * it doesn't need to look up the dictionary for each access
				 * as the pointer to the association is in the literal frame */

				if ((bcode >> 3) & 1)
				{
					hak_oop_t v;

					/* store or pop */
					v = HAK_STACK_GETTOP(hak);
					if (HAK_IS_CLASS(hak, ass->cdr) && ((hak_oop_class_t)ass->cdr)->name == ass->car && v != ass->cdr)
					{
						/* the existing value is a class.
						 * the class name is the same as the key value of the pair.
						 * disallow re-definition if the new value is not itself. */
						hak_seterrbfmt(hak, HAK_EPERM, "prohibited redefintion of %.*js", HAK_OBJ_GET_SIZE(ass->car), HAK_OBJ_GET_CHAR_SLOT(ass->car));
						if (do_throw_with_internal_errmsg(hak, fetched_instruction_pointer) >= 0) break;
						goto oops_with_errmsg_supplement;
					}

					ass->cdr = v; /* update the value */
					if ((bcode >> 2) & 1)
					{
						/* pop */
						LOG_INST_1(hak, "pop_into_object @%zu", b1);
						HAK_STACK_POP(hak);
					}
					else
					{
						LOG_INST_1(hak, "store_into_object @%zu", b1);
					}
				}
				else
				{
					/* push */
					LOG_INST_1(hak, "push_object @%zu", b1);
					if (HAK_IS_UNDEF(hak, ass->cdr))
					{
						hak_seterrbfmt(hak, HAK_EUNDEFVAR, "%.*js accessed without initialization", HAK_OBJ_GET_SIZE(ass->car), HAK_OBJ_GET_CHAR_SLOT(ass->car));
						if (do_throw_with_internal_errmsg(hak, fetched_instruction_pointer) >= 0) break;
						goto oops_with_errmsg_supplement;
					}
					HAK_STACK_PUSH(hak, ass->cdr);
				}
				break;
			}

			/* -------------------------------------------------------- */

			case HAK_CODE_JUMP_FORWARD_X:
				FETCH_PARAM_CODE_TO(hak, b1);
				LOG_INST_1(hak, "jump_forward %zu", b1);
				hak->ip += b1;
				break;

			case HAK_CODE_JUMP_FORWARD_0:
			case HAK_CODE_JUMP_FORWARD_1:
			case HAK_CODE_JUMP_FORWARD_2:
			case HAK_CODE_JUMP_FORWARD_3:
				LOG_INST_1(hak, "jump_forward %zu", (hak_oow_t)(bcode & 0x3));
				hak->ip += (bcode & 0x3); /* low 2 bits */
				break;

			case HAK_CODE_JUMP_BACKWARD_X:
				FETCH_PARAM_CODE_TO(hak, b1);
				LOG_INST_1(hak, "jump_backward %zu", b1);
				hak->ip -= b1;
				break;

			case HAK_CODE_JUMP_BACKWARD_0:
			case HAK_CODE_JUMP_BACKWARD_1:
			case HAK_CODE_JUMP_BACKWARD_2:
			case HAK_CODE_JUMP_BACKWARD_3:
				LOG_INST_1(hak, "jump_backward %zu", (hak_oow_t)(bcode & 0x3));
				hak->ip -= (bcode & 0x3); /* low 2 bits */
				break;

			case HAK_CODE_JUMP_FORWARD_IF_TRUE:
				FETCH_PARAM_CODE_TO(hak, b1);
				LOG_INST_1(hak, "jump_forward_if_true %zu", b1);
				/*if (HAK_STACK_GETTOP(hak) == hak->_true) hak->ip += b1; TODO: _true or not _false?*/
				if (HAK_STACK_GETTOP(hak) != hak->_false) hak->ip += b1;
				break;

			case HAK_CODE_JUMP2_FORWARD_IF_TRUE:
				FETCH_PARAM_CODE_TO(hak, b1);
				LOG_INST_1(hak, "jump2_forward_if_true %zu", b1);
				/*if (HAK_STACK_GETTOP(hak) == hak->_true) hak->ip += MAX_CODE_JUMP + b1;*/
				if (HAK_STACK_GETTOP(hak) != hak->_false) hak->ip += MAX_CODE_JUMP + b1;
				break;

			case HAK_CODE_JUMP_FORWARD_IF_FALSE:
				FETCH_PARAM_CODE_TO(hak, b1);
				LOG_INST_1(hak, "jump_forward_if_false %zu", b1);
				if (HAK_STACK_GETTOP(hak) == hak->_false) hak->ip += b1;
				break;

			case HAK_CODE_JUMP2_FORWARD_IF_FALSE:
				FETCH_PARAM_CODE_TO(hak, b1);
				LOG_INST_1(hak, "jump2_forward_if_false %zu", b1);
				if (HAK_STACK_GETTOP(hak) == hak->_false) hak->ip += MAX_CODE_JUMP + b1;
				break;

			case HAK_CODE_JUMP2_FORWARD:
				FETCH_PARAM_CODE_TO(hak, b1);
				LOG_INST_1(hak, "jump2_forward %zu", b1);
				hak->ip += MAX_CODE_JUMP + b1;
				break;

			case HAK_CODE_JUMP_BACKWARD_IF_TRUE:
				FETCH_PARAM_CODE_TO(hak, b1);
				LOG_INST_1(hak, "jump_backward_if_true %zu", b1);
				if (HAK_STACK_GETTOP(hak) != hak->_false) hak->ip -= b1;
				break;

			case HAK_CODE_JUMP2_BACKWARD_IF_TRUE:
				FETCH_PARAM_CODE_TO(hak, b1);
				LOG_INST_1(hak, "jump2_backward_if_true %zu", b1);
				if (HAK_STACK_GETTOP(hak) != hak->_false) hak->ip -= MAX_CODE_JUMP + b1;
				break;

			case HAK_CODE_JUMP_BACKWARD_IF_FALSE:
				FETCH_PARAM_CODE_TO(hak, b1);
				LOG_INST_1(hak, "jump_backward_if_false %zu", b1);
				if (HAK_STACK_GETTOP(hak) == hak->_false) hak->ip -= b1;
				break;

			case HAK_CODE_JUMP2_BACKWARD_IF_FALSE:
				FETCH_PARAM_CODE_TO(hak, b1);
				LOG_INST_1(hak, "jump2_backward_if_false %zu", b1);
				if (HAK_STACK_GETTOP(hak) == hak->_false) hak->ip -= MAX_CODE_JUMP + b1;
				break;

			case HAK_CODE_JUMP2_BACKWARD:
				FETCH_PARAM_CODE_TO(hak, b1);
				LOG_INST_1(hak, "jump2_backward %zu", b1);
				hak->ip -= MAX_CODE_JUMP + b1;
				break;

			/* -------------------------------------------------------- */

			case HAK_CODE_PUSH_RETURN_R:
			{
				hak_oop_context_t ctx;
				hak_oow_t i;
				hak_ooi_t attr_mask, fixed_nargs, req_nrets;

				LOG_INST_0(hak, "push_return_r");

				HAK_ASSERT(hak, HAK_IS_CONTEXT(hak, hak->active_context));

				ctx = hak->active_context;

				attr_mask = HAK_OOP_TO_SMOOI(ctx->attr_mask);
				fixed_nargs = GET_BLK_MASK_NARGS(attr_mask);

				req_nrets = HAK_OOP_TO_SMOOI(ctx->req_nrets);

				if (req_nrets <= 0)
				{
					/* if a function with return variables is called in the single-return value call style,
					 * req_nrets becomes 0. but this instruction has to push one value in such a case */
					req_nrets = 1;
				}

				/* return variables are placed after the fixed arguments */
				for (i = 0; i < req_nrets; i++)
				{
					HAK_STACK_PUSH(hak, ctx->slot[fixed_nargs + i]);
				}

				/* similar to HAK_CODE_RETURN_FROM_BLOCK */
				hak->last_retv = ctx->slot[fixed_nargs]; /* remember the first pushed one as the last return value. currently no good way to hak_execute() recognize multiple return values. */
				do_return_from_block(hak);

				break;
			}

			case HAK_CODE_CALL_R:
			{
				hak_oop_t rcv;

				FETCH_PARAM_CODE_TO(hak, b1); /* nargs */
				FETCH_PARAM_CODE_TO(hak, b2); /* nrvars */
				LOG_INST_2(hak, "call %zu %zu", b1, b2);

				rcv = HAK_STACK_GETOP(hak, b1);
				if (HAK_IS_COMPILED_BLOCK(hak, rcv))
				{
					if (activate_block(hak, b1, b2) <= -1) goto call2_failed;
					break;
				}
				else
				{
					hak_seterrbfmt(hak, HAK_ECALL, "cannot call %O", rcv);
				call2_failed:
					goto oops_with_errmsg_supplement;
				}

				break;
			}

			case HAK_CODE_CALL_X:
				FETCH_PARAM_CODE_TO(hak, b1);
				goto handle_call;
			case HAK_CODE_CALL_0:
			case HAK_CODE_CALL_1:
			case HAK_CODE_CALL_2:
			case HAK_CODE_CALL_3:
			{
				hak_oop_t op;

				b1 = bcode & 0x3; /* low 2 bits */
			handle_call:
				LOG_INST_1(hak, "call %zu", b1);

			/* TODO: check if the rcv is the dummy receiver
				rcv = HAK_STACK_GETRCV(hak, b1);
			 * */
				op = HAK_STACK_GETOP(hak, b1);
				if (HAK_OOP_IS_POINTER(op))
				{
					hak_oop_class_t c;
					c = (hak_oop_class_t)HAK_OBJ_GET_CLASS(op);
/* TODO: use class to check? no more ibrand? */
					switch (HAK_OOP_TO_SMOOI(c->ibrand))
					{
						case HAK_BRAND_FUNCTION:
							if (activate_function(hak, b1) <= -1) goto call_failed;
							break;

						case HAK_BRAND_BLOCK:
							if (activate_block(hak, b1, 0) <= -1) goto call_failed;
							break;

						case HAK_BRAND_PRIM:
							if (call_primitive(hak, b1) <= -1)
							{
							/* TODO: do i have tell a catchable exception from a fatal error? */
								if (do_throw_with_internal_errmsg(hak, fetched_instruction_pointer) >= 0) break;
								goto call_failed;
							}
							break;

#if defined(ENABLE_SYSCMD)
						case HAK_BRAND_STRING:
						case HAK_BRAND_SYMBOL:
							if (exec_syscmd(hak, b1) <= -1)
							{
								if (do_throw_with_internal_errmsg(hak, fetched_instruction_pointer) >= 0) break;
								goto call_failed;
							}
							break;
#endif

						default:
							goto cannot_call;
					}
				}
				else
				{
				cannot_call:
					/* run time error */
					hak_seterrbfmt(hak, HAK_ECALL, "cannot call %O", op);
					if (do_throw_with_internal_errmsg(hak, fetched_instruction_pointer) >= 0) break;
				call_failed:
					goto oops_with_errmsg_supplement;
				}
				break;
			}

			/* -------------------------------------------------------- */
			case HAK_CODE_TRY_ENTER:
			{
				hak_ooi_t catch_ip, clsp;

				FETCH_PARAM_CODE_TO(hak, b1);
				LOG_INST_1(hak, "try_enter %zu", b1);

				catch_ip = hak->ip + b1;
				/* TODO: ip overflow check? */
				clsp = HAK_CLSTACK_GET_SP(hak);

				HAK_EXSTACK_PUSH(hak, hak->active_context, catch_ip, clsp, hak->sp);
				break;
			}

			case HAK_CODE_TRY_ENTER2:
			{
				hak_ooi_t catch_ip, clsp;

				FETCH_PARAM_CODE_TO(hak, b1);
				LOG_INST_1(hak, "try_enter2 %zu", b1);

				catch_ip = hak->ip + MAX_CODE_JUMP + b1;
				/* TODO: ip overflow check? */
				clsp = HAK_CLSTACK_GET_SP(hak);

				HAK_EXSTACK_PUSH(hak, hak->active_context, catch_ip, clsp, hak->sp);
				break;
			}

			case HAK_CODE_TRY_EXIT:
				LOG_INST_0(hak, "try_exit");
				/* TODO: stack underflow check? */
				HAK_EXSTACK_POP(hak);
				break;

			case HAK_CODE_THROW:
				LOG_INST_0(hak, "throw");
				return_value = HAK_STACK_GETTOP(hak);
				HAK_STACK_POP(hak);
				if (do_throw(hak, return_value, fetched_instruction_pointer) <= -1) goto oops;
				break;
			/* -------------------------------------------------------- */

			case HAK_CODE_CLASS_LOAD:
			{
				hak_oop_t t;

				/* push the class off the stack top on the class stack */
				LOG_INST_0(hak, "class_load");

				HAK_STACK_POP_TO(hak, t);
				if (!HAK_IS_CLASS(hak, t))
				{
					/*hak_seterrbfmt(hak, HAK_EUNDEFVAR, "%.*js is not class", HAK_OBJ_GET_SIZE(t->car), HAK_OBJ_GET_CHAR_SLOT(t->car));*/
					hak_seterrbfmt(hak, HAK_EUNDEFVAR, "not class"); /* TODO: change error code */
					if (do_throw_with_internal_errmsg(hak, fetched_instruction_pointer) >= 0) break;
					goto oops_with_errmsg_supplement;
				}
				HAK_CLSTACK_PUSH(hak, t);
				break;
			}

			case HAK_CODE_CLASS_ENTER:
			{
				/* push an association with class_name as a key or push nil
				   push superclass (only if nsuperclassses > 0)
				   push ivars_string
				   push cvars_string
				   class_enter nsuperclasses nivars ncvars spec/selfspec, indexed_tye
				 */
				hak_oop_t superclass, ivars_str, cvars_str, class_name, v;
				hak_ooi_t expected_spec, expected_selfspec;
				hak_oop_class_t class_obj;
				hak_oow_t b3, b4, b5;

				FETCH_PARAM_CODE_TO(hak, b1); /* nsuperclasses */
				FETCH_PARAM_CODE_TO(hak, b2); /* nivars */
				FETCH_PARAM_CODE_TO(hak, b3); /* ncvars */
				FETCH_BYTE_CODE_TO(hak, b4); /* spec/selfspec */
				FETCH_BYTE_CODE_TO(hak, b5); /* indexed_type */

				LOG_INST_5(hak, "class_enter %zu %zu %zu %#zx %zu", b1, b2, b3, b4, b5);

				if (b3 > 0)
				{
					HAK_STACK_POP_TO(hak, cvars_str);
					HAK_ASSERT(hak, HAK_IS_STRING(hak, cvars_str));
				}
				else cvars_str = hak->_nil;

				if (b2 > 0)
				{
					HAK_STACK_POP_TO(hak, ivars_str);
					HAK_ASSERT(hak, HAK_IS_STRING(hak, ivars_str));
				}
				else ivars_str = hak->_nil;

				if (b1 > 0)
				{
					hak_ooi_t super_spec;
					hak_obj_type_t super_indexed_type;

					HAK_STACK_POP_TO(hak, superclass); /* TODO: support more than 1 superclass later when the compiler supports more */
					if (!HAK_IS_CLASS(hak, superclass))
					{
						hak_seterrbfmt(hak, HAK_ECALL, "invalid superclass %O", superclass);
						if (do_throw_with_internal_errmsg(hak, fetched_instruction_pointer) >= 0) break;
						goto oops_with_errmsg_supplement;
					}

					super_spec = HAK_OOP_TO_SMOOI(((hak_oop_class_t)superclass)->spec);
					super_indexed_type = HAK_CLASS_SPEC_INDEXED_TYPE(super_spec);
					if (HAK_CLASS_SPEC_NAMED_INSTVARS(super_spec) > 0 && super_indexed_type != b5)
					{
						/* TODO: include the class indexed type in the message .. */
						hak_seterrbfmt(hak, HAK_ECALL, "incompatible %hs superclass %O with %hs class",
							hak_obj_type_to_bcstr(super_indexed_type), superclass, hak_obj_type_to_bcstr(b5));
						if (do_throw_with_internal_errmsg(hak, fetched_instruction_pointer) >= 0) break;
						goto oops_with_errmsg_supplement;
					}
 				}
				else superclass = hak->_nil;

				expected_spec = HAK_CLASS_SPEC_MAKE(b2, ((b4 >> 4) & 0x0F), (b5 & 0xFF));
				expected_selfspec = HAK_CLASS_SELFSPEC_MAKE(b3, 0, (b4 & 0x0F));

				HAK_STACK_POP_TO(hak, v);
				if (HAK_IS_CONS(hak, v))
				{
					/* named class. the compiler generates code to push a pair
					 * holding a name and a class object for a name class. */
					class_name = HAK_CONS_CAR(v);
					HAK_ASSERT(hak, HAK_IS_SYMBOL(hak, class_name));

					class_obj = (hak_oop_class_t)HAK_CONS_CDR(v);
					if (HAK_IS_CLASS(hak, class_obj))
					{
						/* the existing value must be a class. disallow re-definition */

						/* 0(non-kernel object), 1(incomplete kernel object), 2(complete kernel object) */
						if (HAK_OBJ_GET_FLAGS_KERNEL(class_obj) == 1)
						{
							/* check if the new definition is compatible with kernel definition */
							hak_ooi_t spec, selfspec, nivars_super, nivars_super_real;

							spec = HAK_OOP_TO_SMOOI(class_obj->spec);
							selfspec = HAK_OOP_TO_SMOOI(class_obj->selfspec);
							nivars_super = HAK_OOP_TO_SMOOI(class_obj->nivars_super);
							nivars_super_real = HAK_IS_NIL(hak, superclass)? 0: HAK_OOP_TO_SMOOI(((hak_oop_class_t)superclass)->nivars_super);
#if 0
hak_logbfmt(hak, HAK_LOG_STDERR, ">>>%O c->sc=%O sc=%O b2=%d b3=%d nivars=%d ncvars=%d<<<\n", class_obj, class_obj->superclass, superclass, b2, b3, (int)HAK_CLASS_SPEC_NAMED_INSTVARS(spec), (int)HAK_CLASS_SELFSPEC_CLASSVARS(spec));
#endif
							if (class_obj->superclass != superclass ||
							    expected_spec != spec ||
							    expected_selfspec != selfspec ||
							    nivars_super != nivars_super_real)
							{
								hak_seterrbfmt(hak, HAK_EPERM, "incompatible redefintion of %.*js", HAK_OBJ_GET_SIZE(class_name), HAK_OBJ_GET_CHAR_SLOT(class_name));
								if (do_throw_with_internal_errmsg(hak, fetched_instruction_pointer) >= 0) break;
								goto oops_with_errmsg_supplement;
							}
						}
						else
						{
							hak_seterrbfmt(hak, HAK_EPERM, "prohibited redefintion of %.*js", HAK_OBJ_GET_SIZE(class_name), HAK_OBJ_GET_CHAR_SLOT(class_name));
							if (do_throw_with_internal_errmsg(hak, fetched_instruction_pointer) >= 0) break;
							goto oops_with_errmsg_supplement;
						}
					}
					else
					{
						HAK_ASSERT(hak, HAK_IS_NIL(hak, (hak_oop_t)class_obj));
						goto make_class;
					}
				}
				else
				{
					/* anonymous class */
					HAK_ASSERT(hak, HAK_IS_NIL(hak, v));
					class_name = hak->_nil;

				make_class:
					class_obj = (hak_oop_class_t)hak_makeclass(hak, class_name, superclass, expected_spec, expected_selfspec, ivars_str, cvars_str);
					if (HAK_UNLIKELY(!class_obj)) goto oops_with_errmsg_supplement;
				}

				/* push the class created to the class stack.
				 * but don't push to the normal operation stack */
				HAK_CLSTACK_PUSH(hak, (hak_oop_t)class_obj);
				break;
			}

			case HAK_CODE_CLASS_EXIT:
			{
				LOG_INST_0(hak, "class_exit");
				if (HAK_CLSTACK_IS_EMPTY(hak))
				{
					hak_seterrbfmt(hak, HAK_ESTKUNDFLW, "class stack underflow");
					goto oops_with_errmsg_supplement;
				}
				HAK_CLSTACK_POP(hak);
				break;
			}

			case HAK_CODE_CLASS_PEXIT: /* pop + exit */
			{
				hak_oop_t c;

				LOG_INST_0(hak, "class_pexit");

				if (HAK_CLSTACK_IS_EMPTY(hak))
				{
					hak_seterrbfmt(hak, HAK_ESTKUNDFLW, "class stack underflow");
					goto oops_with_errmsg_supplement;
				}
				HAK_CLSTACK_POP_TO(hak, c);
				HAK_STACK_PUSH(hak, c);

				break;
			}

			case HAK_CODE_CLASS_CMSTORE:
			case HAK_CODE_CLASS_IMSTORE:
			case HAK_CODE_CLASS_CIMSTORE:
			{
				hak_oop_t _class;
				hak_oop_t mdic, blk, name;
				int mtype;
				static const hak_bch_t* pfx[] = { "c", "i", "ci" };

				/* mtype => c: 1, i: 2, ci: (1 | 2) */
				mtype = (bcode - HAK_CODE_CLASS_CMSTORE) + 1;
				HAK_ASSERT(hak, mtype >= 1 && mtype <= 3);
				FETCH_PARAM_CODE_TO(hak, b1);
				LOG_INST_2(hak, "class_%hsmstore @%zu", pfx[mtype - 1], b1);

				/* store the stack top in the member dictionary of the currect class with the key indicated by 'b1' */

				HAK_ASSERT(hak, !HAK_CLSTACK_IS_EMPTY(hak));

				HAK_CLSTACK_FETCH_TOP_TO(hak, _class);
				HAK_ASSERT(hak, HAK_IS_CLASS(hak, _class));

				mdic = ((hak_oop_class_t)_class)->mdic; /* instance-side dictionary */
				HAK_ASSERT(hak, HAK_IS_NIL(hak, mdic) || HAK_IS_DIC(hak, mdic));
				if (HAK_IS_NIL(hak, mdic))
				{
					hak_pushvolat(hak, (hak_oop_t*)&_class);
					mdic = hak_makedic(hak, 16); /* TODO: configurable initial size? */
					hak_popvolat(hak);
					if (HAK_UNLIKELY(!mdic)) goto oops_with_errmsg_supplement;
					((hak_oop_class_t)_class)->mdic = mdic;
				}

				blk = HAK_STACK_GETTOP(hak);
				name = hak->active_function->literal_frame[b1]; /* method name */
				/* put the code at method dictionary
				   pass 1 for class method, 2 for instance method, 3 for class instantiation method */
				if (!hak_putatdic_method(hak, (hak_oop_dic_t)mdic, name, blk, &mtype)) goto oops_with_errmsg_supplement;
				if (mtype && HAK_LOG_ENABLED(hak, HAK_LOG_IC | HAK_LOG_WARN))
				{
					hak_oop_t class_name;
					hak_dbgi_t dbgi;

					class_name = ((hak_oop_class_t)_class)->name;
					HAK_ASSERT(hak, HAK_OBJ_IS_CHAR_POINTER(class_name));

					get_ip_dbgi(hak, hak->active_function, fetched_instruction_pointer, &dbgi);

					if (class_name != hak->_nil)
					{
						hak_logbfmt(hak, HAK_LOG_IC | HAK_LOG_WARN, "Warning - redefined method %.*js:%.*js at %js:%zu\n",
							HAK_OBJ_GET_SIZE(class_name),
							HAK_OBJ_GET_CHAR_SLOT(class_name),
							HAK_OBJ_GET_SIZE(name),
							HAK_OBJ_GET_CHAR_SLOT(name),
							(dbgi.fname? dbgi.fname: oocstr_dash),
							dbgi.sline
						);
					}
					else
					{
						hak_logbfmt(hak, HAK_LOG_IC | HAK_LOG_WARN, "Warning - redefined method %.*js for anonymous class at %js:%zu\n",
							HAK_OBJ_GET_SIZE(name),
							HAK_OBJ_GET_CHAR_SLOT(name),
							(dbgi.fname? dbgi.fname: oocstr_dash),
							dbgi.sline
						);
					}
				}
				break;
			}
			/* -------------------------------------------------------- */

			case HAK_CODE_PUSH_CTXTEMPVAR_X:
			case HAK_CODE_STORE_INTO_CTXTEMPVAR_X:
			case HAK_CODE_POP_INTO_CTXTEMPVAR_X:
				FETCH_PARAM_CODE_TO(hak, b1);
				FETCH_PARAM_CODE_TO(hak, b2);
				goto handle_ctxtempvar;
			case HAK_CODE_PUSH_CTXTEMPVAR_0:
			case HAK_CODE_PUSH_CTXTEMPVAR_1:
			case HAK_CODE_PUSH_CTXTEMPVAR_2:
			case HAK_CODE_PUSH_CTXTEMPVAR_3:
			case HAK_CODE_STORE_INTO_CTXTEMPVAR_0:
			case HAK_CODE_STORE_INTO_CTXTEMPVAR_1:
			case HAK_CODE_STORE_INTO_CTXTEMPVAR_2:
			case HAK_CODE_STORE_INTO_CTXTEMPVAR_3:
			case HAK_CODE_POP_INTO_CTXTEMPVAR_0:
			case HAK_CODE_POP_INTO_CTXTEMPVAR_1:
			case HAK_CODE_POP_INTO_CTXTEMPVAR_2:
			case HAK_CODE_POP_INTO_CTXTEMPVAR_3:
			{
				hak_ooi_t i;
				hak_oop_context_t ctx;

				b1 = bcode & 0x3; /* low 2 bits */
				FETCH_BYTE_CODE_TO(hak, b2);

			handle_ctxtempvar:
				ctx = hak->active_context;
				HAK_ASSERT(hak, (hak_oop_t)ctx != hak->_nil);
				for (i = 0; i < b1; i++)
				{
					ctx = (hak_oop_context_t)ctx->home;
					/* the initial context has nil in the home field.
					 * the loop must not reach beyond the initial context */
					HAK_ASSERT(hak, (hak_oop_t)ctx != hak->_nil);
				}

				if ((bcode >> 3) & 1)
				{
					/* store or pop */
					ctx->slot[b2] = HAK_STACK_GETTOP(hak);

					if ((bcode >> 2) & 1)
					{
						/* pop */
						HAK_STACK_POP(hak);
						LOG_INST_2(hak, "pop_into_ctxtempvar %zu %zu", b1, b2);
					}
					else
					{
						LOG_INST_2(hak, "store_into_ctxtempvar %zu %zu", b1, b2);
					}
				}
				else
				{
					/* push */
					HAK_STACK_PUSH(hak, ctx->slot[b2]);
					LOG_INST_2(hak, "push_ctxtempvar %zu %zu", b1, b2);
				}

				break;
			}
			/* -------------------------------------------------------- */

			case HAK_CODE_PUSH_OBJVAR_X:
			case HAK_CODE_STORE_INTO_OBJVAR_X:
			case HAK_CODE_POP_INTO_OBJVAR_X:
				FETCH_PARAM_CODE_TO(hak, b1);
				FETCH_PARAM_CODE_TO(hak, b2);
				goto handle_objvar;

			case HAK_CODE_PUSH_OBJVAR_0:
			case HAK_CODE_PUSH_OBJVAR_1:
			case HAK_CODE_PUSH_OBJVAR_2:
			case HAK_CODE_PUSH_OBJVAR_3:
			case HAK_CODE_STORE_INTO_OBJVAR_0:
			case HAK_CODE_STORE_INTO_OBJVAR_1:
			case HAK_CODE_STORE_INTO_OBJVAR_2:
			case HAK_CODE_STORE_INTO_OBJVAR_3:
			case HAK_CODE_POP_INTO_OBJVAR_0:
			case HAK_CODE_POP_INTO_OBJVAR_1:
			case HAK_CODE_POP_INTO_OBJVAR_2:
			case HAK_CODE_POP_INTO_OBJVAR_3:
			{
				hak_oop_oop_t t;

				/* b1 -> variable index in the object indicated by b2.
				 * b2 -> object index stored in the literal frame. */
				b1 = bcode & 0x3; /* low 2 bits */
				FETCH_BYTE_CODE_TO(hak, b2);

			handle_objvar:
				/*t = hak->code.lit.arr->slot[b2];*/
				t = (hak_oop_oop_t)hak->active_function->literal_frame[b2];
				HAK_ASSERT(hak, HAK_OBJ_GET_FLAGS_TYPE(t) == HAK_OBJ_TYPE_OOP);
				HAK_ASSERT(hak, b1 < HAK_OBJ_GET_SIZE(t));

				if ((bcode >> 3) & 1)
				{
					/* store or pop */
					t->slot[b1] = HAK_STACK_GETTOP(hak);

					if ((bcode >> 2) & 1)
					{
						/* pop */
						LOG_INST_2(hak, "pop_into_objvar %zu %zu", b1, b2);
						HAK_STACK_POP(hak);
					}
					else
					{
						LOG_INST_2(hak, "store_into_objvar %zu %zu", b1, b2);
					}
				}
				else
				{
					/* push */
					LOG_INST_2(hak, "push_objvar %zu %zu", b1, b2);
					HAK_STACK_PUSH(hak, t->slot[b1]);
				}
				break;
			}

			/* -------------------------------------------------------- */
			case HAK_CODE_SEND_R: /* send message with return variables */
			case HAK_CODE_SEND_TO_SUPER_R:

				FETCH_PARAM_CODE_TO(hak, b1); /* nargs */
				FETCH_PARAM_CODE_TO(hak, b2); /* nrvars */

				LOG_INST_3(hak, "send%hs %zu %zu", (((bcode >> 2) & 1)? "_to_super": ""), b1, b2);
				goto handle_send_2;


			case HAK_CODE_SEND_X:
			case HAK_CODE_SEND_TO_SUPER_X:
				FETCH_PARAM_CODE_TO(hak, b1);
				goto handle_send;

			case HAK_CODE_SEND_0:
			case HAK_CODE_SEND_1:
			case HAK_CODE_SEND_2:
			case HAK_CODE_SEND_3:
			case HAK_CODE_SEND_TO_SUPER_0:
			case HAK_CODE_SEND_TO_SUPER_1:
			case HAK_CODE_SEND_TO_SUPER_2:
			case HAK_CODE_SEND_TO_SUPER_3:
			{
				hak_oop_t rcv, op;

				b1 = bcode & 0x3; /* low 2 bits */
			handle_send:
				b2 = 0;
				LOG_INST_2(hak, "send%hs %zu", (((bcode >> 2) & 1)? "_to_super": ""), b1);

			handle_send_2:
				rcv = HAK_STACK_GETRCV(hak, b1);
				op = HAK_STACK_GETOP(hak, b1);
				if (!HAK_OBJ_IS_CHAR_POINTER(op)) /*if (!HAK_IS_SYMBOL(hak, op))*/
				{
					hak_seterrbfmt(hak, HAK_ECALL, "unable to send %O to %O - invalid message", op, rcv); /* TODO: change to HAK_ESEND?? */
				cannot_send:
					if (do_throw_with_internal_errmsg(hak, fetched_instruction_pointer) >= 0) break;
					goto oops_with_errmsg_supplement;
				}
				else
				{
					if (send_message(hak, rcv, op, ((bcode >> 2) & 1) /* to_super */, b1 /* nargs */, b2 /* nrvars */) <= -1)
					{
						const hak_ooch_t* msg = hak_backuperrmsg(hak);
						hak_seterrbfmt(hak, HAK_ECALL, "unable to send %O to %O - %js", op, rcv, msg); /* TODO: change to HAK_ESEND?? */
						goto cannot_send;
					}
				}
				break;
			}

			/* -------------------------------------------------------- */

			/* access the class variables in the initialization context.
			 * the class object is at the class stack top */
			case HAK_CODE_PUSH_CVAR_I_X:
			{
				hak_oop_t t;
				FETCH_PARAM_CODE_TO(hak, b1);
				LOG_INST_1(hak, "push_cvar_i %zu", b1);
				HAK_CLSTACK_FETCH_TOP_TO(hak, t);
				HAK_ASSERT(hak, HAK_IS_CLASS(hak, t));
				HAK_STACK_PUSH(hak, ((hak_oop_class_t)t)->cvar[b1]);
				break;
			}

			case HAK_CODE_STORE_INTO_CVAR_I_X:
			{
				hak_oop_t t;
				FETCH_PARAM_CODE_TO(hak, b1);
				LOG_INST_1(hak, "store_into_cvar_i %zu", b1);
				if (HAK_CLSTACK_IS_EMPTY(hak))
				{
					hak_seterrbfmt(hak, HAK_ESTKUNDFLW, "empty class stack");
				/* TODO: do throw??? instead */
					goto oops_with_errmsg_supplement;
				}
				HAK_CLSTACK_FETCH_TOP_TO(hak, t);
				HAK_ASSERT(hak, HAK_IS_CLASS(hak, t));
				((hak_oop_class_t)t)->cvar[b1] = HAK_STACK_GETTOP(hak);
				break;
			}

			case HAK_CODE_POP_INTO_CVAR_I_X:
			{
				hak_oop_t t;
				FETCH_PARAM_CODE_TO(hak, b1);
				LOG_INST_1(hak, "pop_into_cvar_i %zu", b1);
				if (HAK_CLSTACK_IS_EMPTY(hak))
				{
					hak_seterrbfmt(hak, HAK_ESTKUNDFLW, "empty class stack");
				/* TODO: do throw??? instead */
					goto oops_with_errmsg_supplement;
				}
				HAK_CLSTACK_FETCH_TOP_TO(hak, t);
				HAK_ASSERT(hak, HAK_IS_CLASS(hak, t));
				((hak_oop_class_t)t)->cvar[b1] = HAK_STACK_GETTOP(hak);
				HAK_STACK_POP(hak);
				break;
			}

			/* -------------------------------------------------------- */


			/* access class variables referenced in a method context.
			 * the class variables slots in the owning class of the method that triggerred the current active context */
			case HAK_CODE_PUSH_CVAR_M_X:
			{
				hak_oop_t t;
				FETCH_PARAM_CODE_TO(hak, b1);
				LOG_INST_1(hak, "push_cvar_m %zu", b1);
				HAK_ASSERT(hak, (hak_oop_t)hak->active_context/*->mthhome*/ != hak->_nil);
				t = hak->active_context/*->mthhome*/->owner;
				if (HAK_UNLIKELY(!HAK_IS_CLASS(hak, t)))
				{
					/* this is an internal error or the bytecodes are compromised */
					hak_seterrbfmt(hak, HAK_EINTERN, "non-class owner in class variable access");
					goto oops_with_errmsg_supplement;
				}
				HAK_STACK_PUSH(hak, ((hak_oop_class_t)t)->cvar[b1]);
				break;
			}

			case HAK_CODE_STORE_INTO_CVAR_M_X:
			{
				hak_oop_t t;
				FETCH_PARAM_CODE_TO(hak, b1);
				LOG_INST_1(hak, "store_into_cvar_m %zu", b1);
				HAK_ASSERT(hak, (hak_oop_t)hak->active_context/*->mthhome*/ != hak->_nil);
				t = hak->active_context/*->mthhome*/->owner;
				if (HAK_UNLIKELY(!HAK_IS_CLASS(hak, t)))
				{
					/* this is an internal error or the bytecodes are compromised */
					hak_seterrbfmt(hak, HAK_EINTERN, "non-class owner in class variable access");
					goto oops_with_errmsg_supplement;
				}
				((hak_oop_class_t)t)->cvar[b1] = HAK_STACK_GETTOP(hak);
				break;
			}

			case HAK_CODE_POP_INTO_CVAR_M_X:
			{
				hak_oop_t t;
				FETCH_PARAM_CODE_TO(hak, b1);
				LOG_INST_1(hak, "pop_into_cvar_m %zu", b1);
				HAK_ASSERT(hak, (hak_oop_t)hak->active_context/*->mthhome*/ != hak->_nil);
				t = hak->active_context/*->mthhome*/->owner;
				if (HAK_UNLIKELY(!HAK_IS_CLASS(hak, t)))
				{
					/* this is an internal error or the bytecodes are compromised */
					hak_seterrbfmt(hak, HAK_EINTERN, "non-class owner in class variable access");
					goto oops_with_errmsg_supplement;
				}
				((hak_oop_class_t)t)->cvar[b1] = HAK_STACK_GETTOP(hak);
				HAK_STACK_POP(hak);
				break;
			}

			/* -------------------------------------------------------- */

			case HAK_CODE_PUSH_RECEIVER: /* push self or super */
				LOG_INST_0(hak, "push_receiver");
				HAK_STACK_PUSH(hak, hak->active_context->receiver);
				break;

			case HAK_CODE_PUSH_NIL:
				LOG_INST_0(hak, "push_nil");
				HAK_STACK_PUSH(hak, hak->_nil);
				break;

			case HAK_CODE_PUSH_TRUE:
				LOG_INST_0(hak, "push_true");
				HAK_STACK_PUSH(hak, hak->_true);
				break;

			case HAK_CODE_PUSH_FALSE:
				LOG_INST_0(hak, "push_false");
				HAK_STACK_PUSH(hak, hak->_false);
				break;

			case HAK_CODE_PUSH_CONTEXT:
				LOG_INST_0(hak, "push_context");
				HAK_STACK_PUSH(hak, (hak_oop_t)hak->active_context);
				break;

			case HAK_CODE_PUSH_PROCESS:
				LOG_INST_0(hak, "push_process");
				HAK_STACK_PUSH(hak, (hak_oop_t)hak->processor->active);
				break;

			case HAK_CODE_PUSH_NEGONE:
				LOG_INST_0(hak, "push_negone");
				HAK_STACK_PUSH(hak, HAK_SMOOI_TO_OOP(-1));
				break;

			case HAK_CODE_PUSH_ZERO:
				LOG_INST_0(hak, "push_zero");
				HAK_STACK_PUSH(hak, HAK_SMOOI_TO_OOP(0));
				break;

			case HAK_CODE_PUSH_ONE:
				LOG_INST_0(hak, "push_one");
				HAK_STACK_PUSH(hak, HAK_SMOOI_TO_OOP(1));
				break;

			case HAK_CODE_PUSH_TWO:
				LOG_INST_0(hak, "push_two");
				HAK_STACK_PUSH(hak, HAK_SMOOI_TO_OOP(2));
				break;

			case HAK_CODE_PUSH_INTLIT:
				FETCH_PARAM_CODE_TO(hak, b1);
				LOG_INST_1(hak, "push_intlit %zu", b1);
				HAK_STACK_PUSH(hak, HAK_SMOOI_TO_OOP(b1));
				break;

			case HAK_CODE_PUSH_NEGINTLIT:
			{
				hak_ooi_t num;
				FETCH_PARAM_CODE_TO(hak, b1);
				num = b1;
				LOG_INST_1(hak, "push_negintlit %zu", b1);
				HAK_STACK_PUSH(hak, HAK_SMOOI_TO_OOP(-num));
				break;
			}

			case HAK_CODE_PUSH_CHARLIT:
				FETCH_PARAM_CODE_TO(hak, b1);
				LOG_INST_1(hak, "push_charlit %zu", b1);
				HAK_STACK_PUSH(hak, HAK_CHAR_TO_OOP(b1));
				break;
			/* -------------------------------------------------------- */

			case HAK_CODE_MAKE_ARRAY:
			{
				hak_oop_t t;

				FETCH_PARAM_CODE_TO(hak, b1);
				LOG_INST_1(hak, "make_array %zu", b1);

				/* create an empty array */
				t = hak_makearray(hak, b1);
				if (HAK_UNLIKELY(!t)) goto oops_with_errmsg_supplement;

				HAK_STACK_PUSH(hak, t); /* push the array created */
				break;
			}

			case HAK_CODE_POP_INTO_ARRAY:
			{
				hak_oop_t t1, t2;
				FETCH_PARAM_CODE_TO(hak, b1);
				LOG_INST_1(hak, "pop_into_array %zu", b1);
				t1 = HAK_STACK_GETTOP(hak); /* value to store */
				HAK_STACK_POP(hak);
				t2 = HAK_STACK_GETTOP(hak); /* array */
				if (HAK_UNLIKELY(b1 >= HAK_OBJ_GET_SIZE(t2)))
				{
					hak_seterrbfmt(hak, HAK_ECALL, "array index %zu out of upper bound %zd ", b1, (hak_oow_t)HAK_OBJ_GET_SIZE(t2));
					if (do_throw_with_internal_errmsg(hak, fetched_instruction_pointer) >= 0) break;
					goto oops_with_errmsg_supplement;
				}

				((hak_oop_oop_t)t2)->slot[b1] = t1;
				break;
			}

			case HAK_CODE_MAKE_BYTEARRAY:
			{
				hak_oop_t t;

				FETCH_PARAM_CODE_TO(hak, b1);
				LOG_INST_1(hak, "make_bytearray %zu", b1);

				/* create an empty array */
				t = hak_makebytearray(hak, HAK_NULL, b1);
				if (HAK_UNLIKELY(!t)) goto oops_with_errmsg_supplement;

				HAK_STACK_PUSH(hak, t); /* push the byte array created */
				break;
			}

			case HAK_CODE_POP_INTO_BYTEARRAY:
			{
				hak_oop_t t1, t2;
				hak_ooi_t bv;

				FETCH_PARAM_CODE_TO(hak, b1);
				LOG_INST_1(hak, "pop_into_bytearray %zu", b1);

				t1 = HAK_STACK_GETTOP(hak); /* value to store */
				if (!HAK_OOP_IS_SMOOI(t1) || (bv = HAK_OOP_TO_SMOOI(t1)) < 0 || bv > 255)
				{
					hak_seterrbfmt(hak, HAK_ERANGE, "not a byte or out of byte range - %O", t1);
					if (do_throw_with_internal_errmsg(hak, fetched_instruction_pointer) >= 0) break;
					goto oops_with_errmsg_supplement;
				}
				HAK_STACK_POP(hak);
				t2 = HAK_STACK_GETTOP(hak); /* byte array */

				if (HAK_UNLIKELY(b1 >= HAK_OBJ_GET_SIZE(t2)))
				{
					hak_seterrbfmt(hak, HAK_ECALL, "bytearray index %zu out of upper bound %zd ", b1, (hak_oow_t)HAK_OBJ_GET_SIZE(t2));
					if (do_throw_with_internal_errmsg(hak, fetched_instruction_pointer) >= 0) break;
					goto oops_with_errmsg_supplement;
				}
				((hak_oop_byte_t)t2)->slot[b1] = bv;
				break;
			}

			case HAK_CODE_MAKE_CHARARRAY:
			{
				hak_oop_t t;

				FETCH_PARAM_CODE_TO(hak, b1);
				LOG_INST_1(hak, "make_chararray %zu", b1);

				/* create an empty array */
				t = hak_makechararray(hak, HAK_NULL, b1);
				if (HAK_UNLIKELY(!t)) goto oops_with_errmsg_supplement;

				HAK_STACK_PUSH(hak, t); /* push the char array created */
				break;
			}

			case HAK_CODE_POP_INTO_CHARARRAY:
			{
				hak_oop_t t1, t2;
				hak_ooi_t bv;

				FETCH_PARAM_CODE_TO(hak, b1);
				LOG_INST_1(hak, "pop_into_chararray %zu", b1);

				t1 = HAK_STACK_GETTOP(hak); /* value to store */
				if (!HAK_OOP_IS_CHAR(t1) || (bv = HAK_OOP_TO_CHAR(t1)) < 0 || bv > 255)
				{
					hak_seterrbfmt(hak, HAK_ERANGE, "not a character or out of character range - %O", t1);
					if (do_throw_with_internal_errmsg(hak, fetched_instruction_pointer) >= 0) break;
					goto oops_with_errmsg_supplement;
				}
				HAK_STACK_POP(hak);
				t2 = HAK_STACK_GETTOP(hak); /* char array */

				if (HAK_UNLIKELY(b1 >= HAK_OBJ_GET_SIZE(t2)))
				{
					hak_seterrbfmt(hak, HAK_ECALL, "character array index %zu out of upper bound %zd ", b1, (hak_oow_t)HAK_OBJ_GET_SIZE(t2));
					if (do_throw_with_internal_errmsg(hak, fetched_instruction_pointer) >= 0) break;
					goto oops_with_errmsg_supplement;
				}
				((hak_oop_char_t)t2)->slot[b1] = bv;
				break;
			}

			case HAK_CODE_MAKE_DIC:
			{
				hak_oop_t t;

				FETCH_PARAM_CODE_TO(hak, b1);
				LOG_INST_1(hak, "make_dic %zu", b1);
				t = (hak_oop_t)hak_makedic(hak, b1 + 10);
				if (HAK_UNLIKELY(!t)) goto oops_with_errmsg_supplement;
				HAK_STACK_PUSH(hak, t);
				break;
			}

			case HAK_CODE_POP_INTO_DIC:
			{
				hak_oop_t t1, t2, t3;

				LOG_INST_0(hak, "pop_into_dic");
				t1 = HAK_STACK_GETTOP(hak); /* value */
				HAK_STACK_POP(hak);
				t2 = HAK_STACK_GETTOP(hak); /* key */
				HAK_STACK_POP(hak);
				t3 = HAK_STACK_GETTOP(hak); /* dictionary */
				if (!hak_putatdic(hak, (hak_oop_dic_t)t3, t2, t1)) goto oops_with_errmsg_supplement;
				break;
			}

			case HAK_CODE_MAKE_CONS:
			{
				hak_oop_t t;

				LOG_INST_0(hak, "make_cons");

				t = hak_makecons(hak, hak->_nil, hak->_nil);
				if (HAK_UNLIKELY(!t)) goto oops_with_errmsg_supplement;

				HAK_STACK_PUSH(hak, t); /* push the head cons cell */
				HAK_STACK_PUSH(hak, hak->_nil); /* sentinnel */
				break;
			}

			case HAK_CODE_POP_INTO_CONS:
			{
				hak_oop_t t1, t2, t3;
				LOG_INST_0(hak, "pop_into_cons");

				t1 = HAK_STACK_GETTOP(hak); /* value to store */
				HAK_STACK_POP(hak);

				t3 = HAK_STACK_GETTOP(hak); /* sentinnel */
				HAK_STACK_POP(hak);

				t2 = HAK_STACK_GETTOP(hak); /* head cons */
				if (HAK_UNLIKELY(!HAK_IS_CONS(hak, t2)))
				{
					hak_seterrbfmt(hak, HAK_EINTERN, "internal error - invalid vm state detected in pop_into_cons");
					goto oops;
				}

				if (t3 == hak->_nil)
				{
					((hak_oop_oop_t)t2)->slot[0] = t1;
					HAK_STACK_PUSH(hak, t2); /* push self again */
				}
				else
				{
					hak_oop_t t;

					hak_pushvolat(hak, &t3);
					t = hak_makecons(hak, t1, hak->_nil);
					hak_popvolat(hak);
					if (HAK_UNLIKELY(!t)) goto oops_with_errmsg_supplement;

					((hak_oop_oop_t)t3)->slot[1] = t;
					HAK_STACK_PUSH(hak, t);
				}

				break;
			}

			case HAK_CODE_POP_INTO_CONS_END:
			{
				hak_oop_t t1, t2, t3;
				LOG_INST_0(hak, "pop_into_cons_end");

				t1 = HAK_STACK_GETTOP(hak); /* value to store */
				HAK_STACK_POP(hak);

				t3 = HAK_STACK_GETTOP(hak); /* sentinnel */
				HAK_STACK_POP(hak);

				t2 = HAK_STACK_GETTOP(hak); /* head cons */
				if (HAK_UNLIKELY(!HAK_IS_CONS(hak, t2)))
				{
					hak_seterrbfmt(hak, HAK_EINTERN, "internal error - invalid vm state detected in pop_into_cons");
					goto oops;
				}

				if (t3 == hak->_nil)
				{
					((hak_oop_oop_t)t2)->slot[0] = t1;
				}
				else
				{
					hak_oop_t t;

					hak_pushvolat(hak, &t3);
					t = hak_makecons(hak, t1, hak->_nil);
					hak_popvolat(hak);
					if (HAK_UNLIKELY(!t)) goto oops_with_errmsg_supplement;

					((hak_oop_oop_t)t3)->slot[1] = t;
				}

				break;
			}

			case HAK_CODE_POP_INTO_CONS_CDR:
			{
				hak_oop_t t1, t2, t3;
				LOG_INST_0(hak, "pop_into_cons_end");

				t1 = HAK_STACK_GETTOP(hak); /* value to store */
				HAK_STACK_POP(hak);

				t3 = HAK_STACK_GETTOP(hak); /* sentinnel */
				HAK_STACK_POP(hak);

				t2 = HAK_STACK_GETTOP(hak); /* head cons */
				if (HAK_UNLIKELY(!HAK_IS_CONS(hak, t2)))
				{
					hak_seterrbfmt(hak, HAK_EINTERN, "internal error - invalid vm state detected in pop_into_cons");
					goto oops;
				}

				if (t3 == hak->_nil)
				{
					((hak_oop_oop_t)t2)->slot[1] = t1;
				}
				else
				{
					((hak_oop_oop_t)t3)->slot[1] = t1;
				}

				/* no push back of the sentinnel */
				break;
			}

			/* -------------------------------------------------------- */
			case HAK_CODE_DUP_STACKTOP:
			{
				hak_oop_t t;
				LOG_INST_0(hak, "dup_stacktop");
				HAK_ASSERT(hak, !HAK_STACK_IS_EMPTY(hak));
				t = HAK_STACK_GETTOP(hak);
				HAK_STACK_PUSH(hak, t);
				break;
			}

			case HAK_CODE_POP_STACKTOP:
				LOG_INST_0(hak, "pop_stacktop");
				HAK_ASSERT(hak, !HAK_STACK_IS_EMPTY(hak));

				/* at the top level, the value is just popped off the stack
				 * after evaluation of an expression. so it's likely the
				 * return value of the last expression unless explicit
				 * returning is performed */
				hak->last_retv = HAK_STACK_GETTOP(hak);
				HAK_STACK_POP(hak);
				break;

			case HAK_CODE_RETURN_STACKTOP:
/* [NOTE] this implements the non-local return. the non-local return is not compatible with stack based try-catch implementation.
 * [TODO] can make it compatiable? */
				LOG_INST_0(hak, "return_stacktop");
				return_value = HAK_STACK_GETTOP(hak);
				HAK_STACK_POP(hak);
				goto handle_return;

			case HAK_CODE_RETURN_RECEIVER:
				LOG_INST_0(hak, "return_receiver");
				return_value = hak->active_context->receiver;

			handle_return:
				hak->last_retv = return_value;
				if (do_return_from_home(hak, return_value, fetched_instruction_pointer) <= -1) goto oops_with_errmsg_supplement;
				break;

			case HAK_CODE_RETURN_FROM_BLOCK:
				LOG_INST_0(hak, "return_from_block");

				HAK_ASSERT(hak, HAK_IS_CONTEXT(hak, hak->active_context));
				hak->last_retv = HAK_STACK_GETTOP(hak); /* get the stack top */
				do_return_from_block(hak);

				break;

			case HAK_CODE_MAKE_FUNCTION:
			{
				hak_oop_function_t funcobj;
				hak_oow_t b3, b4, x;
				hak_oow_t joff;

				/* b1 - block temporaries mask
				 * b2 - block temporaries mask
				 * b3 - literal frame index to name
				 * b4 - literal frame base
				 * b5 - literal frame size */
				FETCH_PARAM_CODE_TO(hak, b1);
				FETCH_PARAM_CODE_TO(hak, x);
				b1 = (b1 << (8 * HAK_CODE_LONG_PARAM_SIZE)) | x;

				FETCH_PARAM_CODE_TO(hak, b2);
				FETCH_PARAM_CODE_TO(hak, x);
				b2 = (b2 << (8 * HAK_CODE_LONG_PARAM_SIZE)) | x;

				FETCH_PARAM_CODE_TO(hak, b3);
				FETCH_PARAM_CODE_TO(hak, b4);

				LOG_INST_8(hak, "make_function %zu %zu %zu %zu %zu %zu %zu %zu",
					GET_BLK_MASK_INSTA(b1),
					GET_BLK_MASK_VA(b1),
					GET_BLK_MASK_NARGS(b1),
					GET_BLK_MASK_NRVARS(b1),
					GET_BLK_MASK_NLVARS(b1),
					b2, b3, b4);

				HAK_ASSERT(hak, b1 >= 0);

				/* the MAKE_FUNCTION instruction is followed by the long JUMP_FORWARD_X instruction.
				 * i can decode the instruction and get the size of instructions of the block context */
				HAK_ASSERT(hak, hak->active_code[hak->ip] == HAK_CODE_JUMP_FORWARD_X);
			#if (HAK_CODE_LONG_PARAM_SIZE == 2)
				/* don't use the FETCH_BYTE_CODE() macros or the like to not increment hak->ip */
				joff = hak->active_code[hak->ip + 1];
				joff = (joff << 8) | hak->active_code[hak->ip + 2];
				/* copy the byte codes from the active context to the new context */
				funcobj = make_function(hak, b4, &hak->active_code[hak->ip + 3], joff, HAK_NULL);
			#else
				joff = hak->active_code[hak->ip + 1];
				funcobj = make_function(hak, b4, &hak->active_code[hak->ip + 2], joff, HAK_NULL);
			#endif
				if (HAK_UNLIKELY(!funcobj)) goto oops;

				fill_function_data(hak, funcobj, b1, b2, hak->active_context, &hak->active_function->literal_frame[b3], b4);

				/* push the new function to the stack of the active context */
				HAK_STACK_PUSH(hak, (hak_oop_t)funcobj);
				break;
			}

			case HAK_CODE_MAKE_BLOCK:
			{
				hak_oow_t x;
				hak_oop_block_t blkobj;

				/* block temporaries mask (extended long) */
				FETCH_PARAM_CODE_TO(hak, b1);
				FETCH_PARAM_CODE_TO(hak, x);
				b1 = (b1 << (8 * HAK_CODE_LONG_PARAM_SIZE)) | x;

				/* literal frame index to name (extended long) */
				FETCH_PARAM_CODE_TO(hak, b2);
				FETCH_PARAM_CODE_TO(hak, x);
				b2 = (b2 << (8 * HAK_CODE_LONG_PARAM_SIZE)) | x;

				LOG_INST_6(hak, "make_block %zu %zu %zu %zu %zu %zu",
					GET_BLK_MASK_INSTA(b1),
					GET_BLK_MASK_VA(b1),
					GET_BLK_MASK_NARGS(b1),
					GET_BLK_MASK_NRVARS(b1),
					GET_BLK_MASK_NLVARS(b1),
					b2);

				HAK_ASSERT(hak, b1 >= 0);

				blkobj = make_compiled_block(hak);
				if (HAK_UNLIKELY(!blkobj)) goto oops;

				/* the long forward jump instruction has the format of
				 *   11000100 KKKKKKKK or 11000100 KKKKKKKK KKKKKKKK
				 * depending on HAK_CODE_LONG_PARAM_SIZE. change 'ip' to point to
				 * the instruction after the jump. */
				fill_block_data(hak, blkobj, b1, b2, hak->ip + HAK_CODE_LONG_PARAM_SIZE + 1, hak->active_context);

				/* push the new block context to the stack of the active context */
				HAK_STACK_PUSH(hak, (hak_oop_t)blkobj);
				break;
			}

			case HAK_CODE_NOOP:
				/* do nothing */
				LOG_INST_0(hak, "noop");
				break;

			default:
				HAK_LOG1(hak, HAK_LOG_IC | HAK_LOG_FATAL, "Fatal error - unknown byte code 0x%zx\n", bcode);
				hak_seterrnum(hak, HAK_EINTERN);
				goto oops;
		}
	}

done:
	hak->gci.lazy_sweep = 1;

	vm_cleanup(hak);
#if defined(HAK_PROFILE_VM)
	HAK_LOG1(hak, HAK_LOG_IC | HAK_LOG_INFO, "EXEC OK - TOTAL INST COUTNER = %zu\n", inst_counter);
#endif
	return 0;

oops_with_errmsg_supplement:
	supplement_errmsg(hak, fetched_instruction_pointer);

oops:
	hak->gci.lazy_sweep = 1;

	vm_cleanup(hak);
#if defined(HAK_PROFILE_VM)
	HAK_LOG1(hak, HAK_LOG_IC | HAK_LOG_INFO, "EXEC ERROR - TOTAL INST COUTNER = %zu\n", inst_counter);
#endif

	return -1;
}

hak_oop_t hak_execute (hak_t* hak)
{
	hak_oop_function_t funcobj;
	int n;
	hak_bitmask_t log_default_type_mask;

	HAK_ASSERT(hak, hak->code.bc.len < HAK_SMOOI_MAX); /* asserted by the compiler */

	log_default_type_mask = hak->log.default_type_mask;
	hak->log.default_type_mask |= HAK_LOG_VM;

	HAK_ASSERT(hak, hak->initial_context == HAK_NULL);
	HAK_ASSERT(hak, hak->active_context == HAK_NULL);

	/* the code generated doesn't cater for its use as an initial funtion.
	 * mutate the generated code so that the intiail function can break
	 * out of the execution loop in execute() smoothly */

	if (hak->code.bc.len > 0)
	{
		HAK_ASSERT(hak, hak->code.bc.ptr[hak->code.bc.len - 1] == HAK_CODE_POP_STACKTOP);
	#if 1
		/* append RETURN_FROM_BLOCK
		 * if (hak_emitbyteinstruction(hak, HAK_CODE_RETURN_FROM_BLOCK) <= -1) return -1;*/
		/* substitute RETURN_FROM_BLOCK for POP_STACKTOP) */
		hak->code.bc.ptr[hak->code.bc.len - 1] = HAK_CODE_RETURN_FROM_BLOCK;
	#else
		/* substitute RETURN_STACKTOP for POP_STACKTOP) */
		hak->code.bc.ptr[hak->code.bc.len - 1] = HAK_CODE_RETURN_STACKTOP;
	#endif
	}

	/* create a virtual function object that holds the byte code generated plus the literal frame.
	 * when hak_execute() is called repeatedly, especially in the interactive mode, functions and
	 * methods defined in the previous call refers to data in the previous virtual function. the
	 * instruction pointers and literal frame indicies, if such functions are called, don't reference
	 * data in this new function. */
	funcobj = make_function(hak, hak->code.lit.len, hak->code.bc.ptr, hak->code.bc.len, hak->code.dbgi);
	if (HAK_UNLIKELY(!funcobj)) return HAK_NULL;

	/* pass nil for the home context of the initial function */
	fill_function_data(
		hak, funcobj, ENCODE_BLK_MASK(0,0,0,0,hak->code.ngtmprs), HAK_TYPE_MAX(hak_oow_t),
		(hak_oop_context_t)hak->_nil, hak->code.lit.arr->slot, hak->code.lit.len);

	hak->initial_function = funcobj; /* the initial function is ready */

#if 0
	/* unless the system is buggy, hak->proc_map_used should be 0.
	 * the standard library terminates all processes before halting.
	 *
	 * [EXPERIMENTAL]
	 * if you like the process allocation to start from 0, uncomment
	 * the following 'if' block */
	if (hak->proc_map_capa > 0 && hak->proc_map_used == 0)
	{
		/* rechain the process map. it must be compatible with prepare_to_alloc_pid().
		 * by placing the low indiced slot at the beginning of the free list,
		 * the special processes (main_proc, gcfin_proc, ossig_proc) are allocated
		 * with low process IDs. */
		hak_ooi_t i, j;

		hak->proc_map_free_first = 0;
		for (i = 0, j = 1; j < hak->proc_map_capa; i++, j++)
		{
			hak->proc_map[i] = HAK_SMOOI_TO_OOP(j);
		}
		hak->proc_map[i] = HAK_SMOOI_TO_OOP(-1);
		hak->proc_map_free_last = i;
	}
#endif

	n = start_initial_process_and_context(hak, 0, hak->code.ngtmprs); /*  set up the initial context over the initial function */
	if (n >= 0)
	{
		hak->last_retv = hak->_nil;
		n = execute(hak);
		HAK_INFO1 (hak, "RETURNED VALUE - %O\n", hak->last_retv);
	}

	hak->initial_context = HAK_NULL;
	hak->active_context = HAK_NULL;

	HAK_ASSERT(hak, hak->processor->active == hak->nil_process);
	HAK_ASSERT(hak, HAK_OOP_TO_SMOOI(hak->processor->total_count) == 0);
	HAK_ASSERT(hak, HAK_OOP_TO_SMOOI(hak->processor->runnable.count) == 0);
	HAK_ASSERT(hak, HAK_OOP_TO_SMOOI(hak->processor->suspended.count) == 0);

	LOAD_ACTIVE_SP(hak); /* sync hak->nil_process->sp with hak->sp */
	HAK_ASSERT(hak, hak->sp == -1);

#if defined(HAK_PROFILE_VM)
	HAK_LOG2(hak, HAK_LOG_IC | HAK_LOG_INFO, "GC - gci.bsz: %zu, gci.stack.max: %zu\n", hak->gci.bsz, hak->gci.stack.max);
	if (hak->heap->xma) hak_xma_dump (hak->heap->xma, xma_dumper, hak);
	HAK_LOG2(hak, HAK_LOG_IC | HAK_LOG_INFO, "GC - gci.stat.alloc: %ld.%09u\n", (unsigned long int)hak->gci.stat.alloc.sec, (unsigned int)hak->gci.stat.alloc.nsec);
	HAK_LOG2(hak, HAK_LOG_IC | HAK_LOG_INFO, "GC - gci.stat.mark: %ld.%09u\n", (unsigned long int)hak->gci.stat.mark.sec, (unsigned int)hak->gci.stat.mark.nsec);
	HAK_LOG2(hak, HAK_LOG_IC | HAK_LOG_INFO, "GC - gci.stat.sweep: %ld.%09u\n", (unsigned long int)hak->gci.stat.sweep.sec, (unsigned int)hak->gci.stat.sweep.nsec);
#endif

	hak->log.default_type_mask = log_default_type_mask;
	return (n <= -1)? HAK_NULL: hak->last_retv;
}

void hak_abort (hak_t* hak)
{
	hak->abort_req = 1;
}

/* ------------------------------------------------------------------ */

hak_pfrc_t hak_pf_process_current (hak_t* hak, hak_mod_t* mod, hak_ooi_t nargs)
{
	HAK_STACK_SETRET(hak, nargs, (hak_oop_t)hak->processor->active);
	return HAK_PF_SUCCESS;
}

hak_pfrc_t hak_pf_process_fork (hak_t* hak, hak_mod_t* mod, hak_ooi_t nargs)
{
	hak_oop_block_t blk;
	hak_oop_context_t newctx;
	hak_oop_process_t newprc;
	int x;

	blk = (hak_oop_block_t)HAK_STACK_GETARG(hak, nargs, 0);
	if (!HAK_IS_COMPILED_BLOCK(hak, blk))
	{
		hak_seterrbfmt(hak, HAK_EINVAL, "parameter not compiled block - %O", blk);
		return HAK_PF_FAILURE;
	}

	/* (defun x(a b) ...)
	 * (fork x 1 2)
	 * among three arguments to fork, the first is the function block.
	 * the remaining two should become arguments to the function block.
	 * pass nargs_offset of 1 to prepare_new_context() to achieve it.
	 */
	x = prepare_new_context(
		hak,
		blk,
		nargs, /* nargs */
		1, /* nargs_offset */
		0, /* number of return variables expected */
		1, /* copy_args */
		0, /* is_msgsend */
		0, /* msg_ivaroff */
		&newctx);
	if (HAK_UNLIKELY(x <= -1)) return HAK_PF_FAILURE;

	HAK_ASSERT(hak, (hak_oop_t)newctx->sender == hak->_nil);
	newctx->home = (hak_oop_context_t)hak->_nil; /* the new context is the initial context in the new process. so reset it to nil */

	hak_pushvolat(hak, (hak_oop_t*)&newctx);
	newprc = make_process(hak, newctx);
	hak_popvolat(hak);
	if (HAK_UNLIKELY(!newprc)) return HAK_PF_FAILURE;

	chain_into_processor(hak, newprc, HAK_PROCESS_STATE_RUNNABLE);

	HAK_STACK_SETRET(hak, nargs, (hak_oop_t)newprc);
	return HAK_PF_SUCCESS;
}

hak_pfrc_t hak_pf_process_resume (hak_t* hak, hak_mod_t* mod, hak_ooi_t nargs)
{
	hak_oop_process_t prc;

	prc = (hak_oop_process_t)HAK_STACK_GETARG(hak, nargs, 0);
	if (!HAK_IS_PROCESS(hak, prc))
	{
		hak_seterrbfmt(hak, HAK_EINVAL, "parameter not process - %O", prc);
		return HAK_PF_FAILURE;
	}

	resume_process(hak, prc);
	return  HAK_PF_SUCCESS;
}

hak_pfrc_t hak_pf_process_suspend (hak_t* hak, hak_mod_t* mod, hak_ooi_t nargs)
{
	hak_oop_process_t prc;

	if (nargs >= 1)
	{
		prc = (hak_oop_process_t)HAK_STACK_GETARG(hak, nargs, 0);
		if (!HAK_IS_PROCESS(hak, prc))
		{
			hak_seterrbfmt(hak, HAK_EINVAL, "parameter not process - %O", prc);
			return HAK_PF_FAILURE;
		}
	}
	else
	{
		prc = hak->processor->active;
	}

	suspend_process(hak, prc);
	return  HAK_PF_SUCCESS;
}

hak_pfrc_t hak_pf_process_terminate (hak_t* hak, hak_mod_t* mod, hak_ooi_t nargs)
{
	hak_oop_process_t prc;

	if (nargs >= 1)
	{
		prc = (hak_oop_process_t)HAK_STACK_GETARG(hak, nargs, 0);
		if (!HAK_IS_PROCESS(hak, prc))
		{
			hak_seterrbfmt(hak, HAK_EINVAL, "parameter not process - %O", prc);
			return HAK_PF_FAILURE;
		}
	}
	else
	{
		prc = hak->processor->active;
	}

	terminate_process(hak, prc);
	return  HAK_PF_SUCCESS;
}

hak_pfrc_t hak_pf_process_terminate_all (hak_t* hak, hak_mod_t* mod, hak_ooi_t nargs)
{
	terminate_all_processes(hak);
	return  HAK_PF_SUCCESS;
}

hak_pfrc_t hak_pf_process_yield (hak_t* hak, hak_mod_t* mod, hak_ooi_t nargs)
{
	yield_process(hak, hak->processor->active);
	return  HAK_PF_SUCCESS;
}

/* ------------------------------------------------------------------ */
hak_pfrc_t hak_pf_semaphore_new (hak_t* hak, hak_mod_t* mod, hak_ooi_t nargs)
{
	hak_oop_semaphore_t sem;

	/*sem = (hak_oop_semaphore_t)hak_allocoopobj(hak, HAK_BRAND_SEMAPHORE, HAK_SEMAPHORE_NAMED_INSTVARS);*/
	sem = (hak_oop_semaphore_t)hak_instantiate(hak, hak->c_semaphore, HAK_NULL, 0);
	if (HAK_UNLIKELY(!sem))
	{
		const hak_ooch_t* oldmsg = hak_backuperrmsg(hak);
		hak_seterrbfmt(hak, hak->errnum,
			"unable to instantiate %O - %js", hak->c_semaphore->name, oldmsg);
		return HAK_PF_FAILURE;
	}

	sem->count = HAK_SMOOI_TO_OOP(0);
/* TODO: sem->signal_action? */
	/* other fields are all set to nil */

	HAK_STACK_SETRET(hak, nargs, (hak_oop_t)sem);
	return HAK_PF_SUCCESS;
}

hak_pfrc_t hak_pf_semaphore_signal (hak_t* hak, hak_mod_t* mod, hak_ooi_t nargs)
{
	hak_oop_semaphore_t sem;
	hak_oop_t sec, nsec;
	hak_ntime_t now, ft;

	sem = (hak_oop_semaphore_t)HAK_STACK_GETARG(hak, nargs, 0);
	if (!HAK_IS_SEMAPHORE(hak, sem))
	{
		hak_seterrbfmt(hak, HAK_EINVAL, "parameter not semaphore - %O", sem);
		return HAK_PF_FAILURE;
	}

	if (nargs <= 1)
	{
		/* signal_semaphore() may change the active process though the
		 * implementation as of this writing makes runnable the process waiting
		 * on the signal to be processed. it is safer to set the return value
		 * before calling signal_sempahore() */
		HAK_STACK_SETRET(hak, nargs, (hak_oop_t)sem);
		signal_semaphore(hak, sem);
		return HAK_PF_SUCCESS;
	}

	sec = HAK_STACK_GETARG(hak, nargs, 1);
	nsec = (nargs >= 3? HAK_STACK_GETARG(hak, nargs, 2): HAK_SMOOI_TO_OOP(0));

	if (!HAK_OOP_IS_SMOOI(sec))
	{
		hak_seterrbfmt(hak, HAK_EINVAL, "invalid second - %O", sec);
		return HAK_PF_FAILURE;
	}

	if (!HAK_OOP_IS_SMOOI(sec))
	{
		hak_seterrbfmt(hak, HAK_EINVAL, "invalid nanosecond - %O", nsec);
		return HAK_PF_FAILURE;
	}

#if 0
	if (sem->subtype == HAK_SMOOI_TO_OOP(HAK_SEMAPHORE_SUBTYPE_TIMED))
	{
		HAK_ASSERT(hak, HAK_OOP_IS_SMOOI(sem->u.timed.index) && HAK_OOP_TO_SMOOI(sem->u.timed.index) >= 0);

		/* if the semaphore is already been added. remove it first */
		delete_from_sem_heap(hak, HAK_OOP_TO_SMOOI(sem->u.timed.index));
		HAK_ASSERT(hak, sem->subtype == hak->_nil && sem->u.timed.index == hak->_nil);

		/*
		Is this more desired???
		HAK_STACK_SETRET(hak, nargs, hak->_false);
		return HAK_PF_SUCCESS;
		*/
	}
#else
	if (sem->subtype != hak->_nil)
	{
		if (sem->subtype == HAK_SMOOI_TO_OOP(HAK_SEMAPHORE_SUBTYPE_IO))
		{
			HAK_ASSERT(hak, HAK_OOP_IS_SMOOI(sem->u.io.index) && HAK_OOP_TO_SMOOI(sem->u.io.index) >= 0);
			HAK_ASSERT(hak, HAK_OOP_IS_SMOOI(sem->u.io.handle) && HAK_OOP_TO_SMOOI(sem->u.io.handle) >= 0);
			HAK_ASSERT(hak, HAK_OOP_IS_SMOOI(sem->u.io.type));
			hak_seterrbfmt(hak, HAK_EINVAL, "semaphore already linked with a handle %zd", HAK_OOP_TO_SMOOI(sem->u.io.handle));
		}
		else
		{
			HAK_ASSERT(hak, sem->subtype == HAK_SMOOI_TO_OOP(HAK_SEMAPHORE_SUBTYPE_TIMED));
			HAK_ASSERT(hak, HAK_OOP_IS_SMOOI(sem->u.timed.index) && HAK_OOP_TO_SMOOI(sem->u.timed.index) >= 0);
			HAK_ASSERT(hak, HAK_OOP_IS_SMOOI(sem->u.timed.ftime_sec));
			HAK_ASSERT(hak, HAK_OOP_IS_SMOOI(sem->u.timed.ftime_nsec));
			hak_seterrbfmt(hak, HAK_EINVAL, "semaphore already activated for timer");
		}

		return HAK_PF_FAILURE;
	}
#endif
	/* this code assumes that the monotonic clock returns a small value
	 * that can fit into a SmallInteger, even after some additions. */
	vm_gettime(hak, &now);
	HAK_ADD_NTIME_SNS (&ft, &now, HAK_OOP_TO_SMOOI(sec), HAK_OOP_TO_SMOOI(nsec));
	if (ft.sec < 0 || ft.sec > HAK_SMOOI_MAX)
	{
		/* soft error - cannot represent the expiry time in a small integer. */
		HAK_LOG2(hak, HAK_LOG_PRIMITIVE | HAK_LOG_ERROR,
			"Error - time (%ld) out of range(0 - %zd) when adding a timed semaphore\n",
			(unsigned long int)ft.sec, (hak_ooi_t)HAK_SMOOI_MAX);

		hak_seterrnum(hak, HAK_ERANGE);
		return HAK_PF_FAILURE;
	}

	sem->u.timed.ftime_sec = HAK_SMOOI_TO_OOP(ft.sec);
	sem->u.timed.ftime_nsec = HAK_SMOOI_TO_OOP(ft.nsec);

	if (add_to_sem_heap(hak, sem) <= -1) return HAK_PF_FAILURE;
	HAK_ASSERT(hak, sem->subtype == HAK_SMOOI_TO_OOP(HAK_SEMAPHORE_SUBTYPE_TIMED));

	HAK_STACK_SETRET(hak, nargs, (hak_oop_t)sem);
	return HAK_PF_SUCCESS;
}

static hak_pfrc_t __semaphore_signal_on_io (hak_t* hak, hak_ooi_t nargs, hak_semaphore_io_type_t io_type)
{
	hak_oop_semaphore_t sem;
	hak_oop_t fd;

	sem = (hak_oop_semaphore_t)HAK_STACK_GETARG(hak, nargs, 0);
	if (!HAK_IS_SEMAPHORE(hak, sem))
	{
		hak_seterrbfmt(hak, HAK_EINVAL, "parameter not semaphore - %O", sem);
		return HAK_PF_FAILURE;
	}

	fd = HAK_STACK_GETARG(hak, nargs, 1);

	if (!HAK_OOP_IS_SMOOI(fd))
	{
		hak_seterrbfmt(hak, HAK_EINVAL, "handle not a small integer - %O", fd);
		return HAK_PF_FAILURE;
	}

	if (sem->subtype != hak->_nil)
	{
		if (sem->subtype == HAK_SMOOI_TO_OOP(HAK_SEMAPHORE_SUBTYPE_IO))
		{
			HAK_ASSERT(hak, HAK_OOP_IS_SMOOI(sem->u.io.index) && HAK_OOP_TO_SMOOI(sem->u.io.index) >= 0);
			HAK_ASSERT(hak, HAK_OOP_IS_SMOOI(sem->u.io.handle) && HAK_OOP_TO_SMOOI(sem->u.io.handle) >= 0);
			HAK_ASSERT(hak, HAK_OOP_IS_SMOOI(sem->u.io.type));
			hak_seterrbfmt(hak, HAK_EINVAL, "semaphore already linked with a handle %zd", HAK_OOP_TO_SMOOI(sem->u.io.handle));
		}
		else
		{
			HAK_ASSERT(hak, sem->subtype == HAK_SMOOI_TO_OOP(HAK_SEMAPHORE_SUBTYPE_TIMED));
			HAK_ASSERT(hak, HAK_OOP_IS_SMOOI(sem->u.timed.index) && HAK_OOP_TO_SMOOI(sem->u.timed.index) >= 0);
			HAK_ASSERT(hak, HAK_OOP_IS_SMOOI(sem->u.timed.ftime_sec));
			HAK_ASSERT(hak, HAK_OOP_IS_SMOOI(sem->u.timed.ftime_nsec));
			hak_seterrbfmt(hak, HAK_EINVAL, "semaphore already activated for timer");
		}

		return HAK_PF_FAILURE;
	}

	if (add_sem_to_sem_io_tuple(hak, sem, HAK_OOP_TO_SMOOI(fd), io_type) <= -1)
	{
		const hak_ooch_t* oldmsg = hak_backuperrmsg(hak);
		hak_seterrbfmt(hak, hak->errnum, "unable to add the handle %zd to the multiplexer for %hs - %js", HAK_OOP_TO_SMOOI(fd), io_type_str[io_type], oldmsg);
		return HAK_PF_FAILURE;
	}

	HAK_STACK_SETRET(hak, nargs, (hak_oop_t)sem);
	return HAK_PF_SUCCESS;
}

hak_pfrc_t hak_pf_semaphore_signal_on_input (hak_t* hak, hak_mod_t* mod, hak_ooi_t nargs)
{
	return __semaphore_signal_on_io(hak, nargs, HAK_SEMAPHORE_IO_TYPE_INPUT);
}

hak_pfrc_t hak_pf_semaphore_signal_on_output (hak_t* hak, hak_mod_t* mod, hak_ooi_t nargs)
{
	return __semaphore_signal_on_io(hak, nargs, HAK_SEMAPHORE_IO_TYPE_OUTPUT);
}

#if 0
hak_pfrc_t hak_pf_semaphore_signal_on_gcfin (hak_t* hak, hak_mod_t* mod, hak_ooi_t nargs)
{
	hak_oop_semaphore_t sem;

	sem = (hak_oop_semaphore_t)HAK_STACK_GETRCV(hak, nargs);
	HAK_PF_CHECK_RCV(hak, hak_iskindof(hak, (hak_oop_t)sem, hak->_semaphore));

/* TODO: should i prevent overwriting? */
	hak->sem_gcfin = sem;

	HAK_STACK_SETRETTORCV(hak, nargs); /* ^self */
	return HAK_PF_SUCCESS;
}
#endif

hak_pfrc_t hak_pf_semaphore_wait (hak_t* hak, hak_mod_t* mod, hak_ooi_t nargs)
{
	hak_oop_semaphore_t sem;

	sem = (hak_oop_semaphore_t)HAK_STACK_GETARG(hak, nargs, 0);
	if (!HAK_IS_SEMAPHORE(hak, sem))
	{
		hak_seterrbfmt(hak, HAK_EINVAL, "parameter not semaphore - %O", sem);
		return HAK_PF_FAILURE;
	}

	if (!can_await_semaphore(hak, sem))
	{
		hak_seterrbfmt(hak, HAK_EPERM, "not allowed to wait on a semaphore that belongs to a semaphore group");
		return HAK_PF_FAILURE;
	}

	/* i must set the return value before calling await_semaphore().
	 * await_semaphore() may switch the active process and the stack
	 * manipulation macros target at the active process. i'm not supposed
	 * to change the return value of a new active process. */
	HAK_STACK_SETRET(hak, nargs, (hak_oop_t)sem);

	await_semaphore(hak, sem);
	return HAK_PF_SUCCESS;
}


hak_pfrc_t hak_pf_semaphore_unsignal (hak_t* hak, hak_mod_t* mod, hak_ooi_t nargs)
{
	/* remove a semaphore from processor's signal scheduling.
	 * it takes no effect on a plain semaphore. */
	hak_oop_semaphore_t sem;

	sem = (hak_oop_semaphore_t)HAK_STACK_GETARG(hak, nargs, 0);
	if (!HAK_IS_SEMAPHORE(hak, sem))
	{
		hak_seterrbfmt(hak, HAK_EINVAL, "parameter not semaphore - %O", sem);
		return HAK_PF_FAILURE;
	}

/*
TODO: add this back if gcfin support is added
	if (sem == hak->sem_gcfin)
	{
		hak->sem_gcfin = (hak_oop_semaphore_t)hak->_nil;
	}
*/

	if (sem->subtype == HAK_SMOOI_TO_OOP(HAK_SEMAPHORE_SUBTYPE_TIMED))
	{
		/* the semaphore is in the timed semaphore heap */
		HAK_ASSERT(hak, HAK_OOP_IS_SMOOI(sem->u.timed.index) && HAK_OOP_TO_SMOOI(sem->u.timed.index) >= 0);
		delete_from_sem_heap(hak, HAK_OOP_TO_SMOOI(sem->u.timed.index));
		HAK_ASSERT(hak, sem->u.timed.index == hak->_nil);
	}
	else if (sem->subtype == HAK_SMOOI_TO_OOP(HAK_SEMAPHORE_SUBTYPE_IO))
	{
		hak_oop_process_t wp; /* waiting process */

		/* the semaphore is associated with IO */
		HAK_ASSERT(hak, HAK_OOP_IS_SMOOI(sem->u.io.index) && HAK_OOP_TO_SMOOI(sem->u.io.index) >= 0);
		HAK_ASSERT(hak, HAK_OOP_IS_SMOOI(sem->u.io.type));
		HAK_ASSERT(hak, HAK_OOP_IS_SMOOI(sem->u.io.handle) && HAK_OOP_TO_SMOOI(sem->u.io.handle) >= 0);

		if (delete_sem_from_sem_io_tuple(hak, sem, 0) <= -1)
		{
			const hak_ooch_t* oldmsg = hak_backuperrmsg(hak);
			hak_seterrbfmt(hak, hak->errnum, "cannot delete the handle %zd from the multiplexer - %js", HAK_OOP_TO_SMOOI(sem->u.io.handle), oldmsg);
			return HAK_PF_FAILURE;
		}

		HAK_ASSERT(hak, (hak_oop_t)sem->u.io.index == hak->_nil);
		HAK_ASSERT(hak, (hak_oop_t)sem->u.io.handle == hak->_nil);

		/* the semaphore gets changed to a plain semaphore after
		 * delete_sem_from_sem_io_tuple(). if there is a process
		 * waiting on this IO semaphore, the process now is treated
		 * as if it's waiting on a plain semaphore. let's adjust
		 * the number of processes waiting on IO semaphores */
		for (wp = sem->waiting.first; (hak_oop_t)wp != hak->_nil; wp = wp->sem_wait.next)
		{
			HAK_ASSERT(hak, hak->sem_io_wait_count > 0);
			hak->sem_io_wait_count--;
		}
	}
	HAK_ASSERT(hak, sem->subtype == hak->_nil);

	HAK_STACK_SETRET(hak, nargs, (hak_oop_t)sem);
	return HAK_PF_SUCCESS;
}

/* ------------------------------------------------------------------ */
hak_pfrc_t hak_pf_semaphore_group_new (hak_t* hak, hak_mod_t* mod, hak_ooi_t nargs)
{
	hak_oop_semaphore_group_t sg;

	/*sg = (hak_oop_semaphore_group_t)hak_allocoopobj(hak, HAK_BRAND_SEMAPHORE_GROUP, HAK_SEMAPHORE_GROUP_NAMED_INSTVARS);*/
	sg = (hak_oop_semaphore_group_t)hak_instantiate(hak, hak->c_semaphore_group, HAK_NULL, 0);
	if (HAK_UNLIKELY(!sg))
	{
		const hak_ooch_t* oldmsg = hak_backuperrmsg(hak);
		hak_seterrbfmt(hak, hak->errnum,
			"unable to instantiate %O - %js", hak->c_semaphore_group->name, oldmsg);
		return HAK_PF_FAILURE;
	}

	sg->sem_io_count = HAK_SMOOI_TO_OOP(0);
	sg->sem_count = HAK_SMOOI_TO_OOP(0);
/* TODO: sem->signal_action? */
	/* other fields are all set to nil */

	HAK_STACK_SETRET(hak, nargs, (hak_oop_t)sg);
	return HAK_PF_SUCCESS;
}

hak_pfrc_t hak_pf_semaphore_group_add_semaphore (hak_t* hak, hak_mod_t* mod, hak_ooi_t nargs)
{
	hak_oop_semaphore_group_t sg;
	hak_oop_semaphore_t sem;

	sg = (hak_oop_semaphore_group_t)HAK_STACK_GETARG(hak, nargs, 0);
	if (!HAK_IS_SEMAPHORE_GROUP(hak, sg))
	{
		hak_seterrbfmt(hak, HAK_EINVAL, "parameter not semaphore group - %O", sg);
		return HAK_PF_FAILURE;
	}

	sem = (hak_oop_semaphore_t)HAK_STACK_GETARG(hak, nargs, 1);
	if (!HAK_IS_SEMAPHORE(hak, sem))
	{
		hak_seterrbfmt(hak, HAK_EINVAL, "parameter not semaphore - %O", sem);
		return HAK_PF_FAILURE;
	}

	if ((hak_oop_t)sem->group == hak->_nil)
	{
		/* the semaphore doesn't belong to a group */
		hak_ooi_t count;
		int sems_idx;

		sems_idx = HAK_OOP_TO_SMOOI(sem->count) > 0? HAK_SEMAPHORE_GROUP_SEMS_SIG: HAK_SEMAPHORE_GROUP_SEMS_UNSIG;
		HAK_APPEND_TO_OOP_LIST(hak, &sg->sems[sems_idx], hak_oop_semaphore_t, sem, grm);
		sem->group = sg;

		count = HAK_OOP_TO_SMOOI(sg->sem_count);
		HAK_ASSERT(hak, count >= 0);
		count++;
		sg->sem_count = HAK_SMOOI_TO_OOP(count);

		if (sem->subtype == HAK_SMOOI_TO_OOP(HAK_SEMAPHORE_SUBTYPE_IO))
		{
			/* the semaphore being added is associated with I/O operation. */
			HAK_ASSERT(hak, HAK_OOP_IS_SMOOI(sem->u.io.index) &&
			                 HAK_OOP_TO_SMOOI(sem->u.io.index) >= 0 &&
			                 HAK_OOP_TO_SMOOI(sem->u.io.index) < hak->sem_io_tuple_count);

			count = HAK_OOP_TO_SMOOI(sg->sem_io_count);
			HAK_ASSERT(hak, count >= 0);
			count++;
			sg->sem_io_count = HAK_SMOOI_TO_OOP(count);

			if (count == 1)
			{
				/* the first IO semaphore is being added to the semaphore group.
				 * but there are already processes waiting on the semaphore group.
				 *
				 * for instance,
				 *  [Process 1]
				 *     sg := SemaphoreGroup new.
				 *     sg wait.
				 *  [Process 2]
				 *     sg addSemaphore: (Semaphore new).
				 */

				hak_oop_process_t wp;
				/* TODO: add sem_wait_count to process. no traversal... */
				for (wp = sg->waiting.first; (hak_oop_t)wp != hak->_nil; wp = wp->sem_wait.next)
				{
					hak->sem_io_wait_count++;
					HAK_DEBUG1 (hak, "hak_pf_semaphore_group_add_semaphore - raised sem_io_wait_count to %zu\n", hak->sem_io_wait_count);
				}
			}
		}

		HAK_STACK_SETRET(hak, nargs, (hak_oop_t)sg);
	}
	else if (sem->group == sg)
	{
		/* do nothing. don't change the group of the semaphore */
		HAK_STACK_SETRET(hak, nargs, (hak_oop_t)sg);
	}
	else
	{
		/* the semaphore belongs to a group already */
/* TODO: is it better to move this semaphore to the new group? */
		hak_seterrbfmt(hak, HAK_EPERM, "not allowed to relocate a semaphore to a different group");
		return HAK_PF_FAILURE;
	}

	return HAK_PF_SUCCESS;
}

hak_pfrc_t hak_pf_semaphore_group_remove_semaphore (hak_t* hak, hak_mod_t* mod, hak_ooi_t nargs)
{
	hak_oop_semaphore_group_t sg;
	hak_oop_semaphore_t sem;
	hak_ooi_t count;

	sg = (hak_oop_semaphore_group_t)HAK_STACK_GETARG(hak, nargs, 0);
	if (!HAK_IS_SEMAPHORE_GROUP(hak, sg))
	{
		hak_seterrbfmt(hak, HAK_EINVAL, "parameter not semaphore group - %O", sg);
		return HAK_PF_FAILURE;
	}

	sem = (hak_oop_semaphore_t)HAK_STACK_GETARG(hak, nargs, 1);
	if (!HAK_IS_SEMAPHORE(hak, sem))
	{
		hak_seterrbfmt(hak, HAK_EINVAL, "parameter not semaphore - %O", sem);
		return HAK_PF_FAILURE;
	}

	if (sem->group == sg)
	{
		int sems_idx;

#if 0
		if ((hak_oop_t)sg->waiting.first != hak->_nil)
		{
			/* there is a process waiting on this semaphore group.
			 * i don't allow a semaphore to be removed from the group.
			 * i want to dodge potential problems arising when removal is allowed.
			 *
			 * for instance, consider this psuedo code.
			 *   sg addSemaphore: s
			 *   [ sg wait ] fork.
			 *   [ sg wait ] fork.
			 *   [ sg wait ] fork.
			 *   sg removeSemaphore: s.
			 *
			 */
			hak_seterrbfmt(hak, HAK_EPERM, "not allowed to remove a semaphore from a group being waited on");
			return HAK_PF_FAILURE;
		}
#endif

		sems_idx = HAK_OOP_TO_SMOOI(sem->count) > 0? HAK_SEMAPHORE_GROUP_SEMS_SIG: HAK_SEMAPHORE_GROUP_SEMS_UNSIG;
		HAK_DELETE_FROM_OOP_LIST(hak, &sg->sems[sems_idx], sem, grm);
		sem->grm.prev = (hak_oop_semaphore_t)hak->_nil;
		sem->grm.next = (hak_oop_semaphore_t)hak->_nil;
		sem->group = (hak_oop_semaphore_group_t)hak->_nil;

		count = HAK_OOP_TO_SMOOI(sg->sem_count);
		HAK_ASSERT(hak, count > 0);
		count--;
		sg->sem_count = HAK_SMOOI_TO_OOP(count);

		if (sem->subtype == HAK_SMOOI_TO_OOP(HAK_SEMAPHORE_SUBTYPE_IO))
		{
			HAK_ASSERT(hak, HAK_OOP_IS_SMOOI(sem->u.io.index) &&
			                HAK_OOP_TO_SMOOI(sem->u.io.index) >= 0 &&
			                HAK_OOP_TO_SMOOI(sem->u.io.index) < hak->sem_io_tuple_count);

			count = HAK_OOP_TO_SMOOI(sg->sem_io_count);
			HAK_ASSERT(hak, count > 0);
			count--;
			sg->sem_io_count = HAK_SMOOI_TO_OOP(count);

			if (count == 0)
			{
				hak_oop_process_t wp;
				/* TODO: add sem_wait_count to process. no traversal... */
				for (wp = sg->waiting.first; (hak_oop_t)wp != hak->_nil; wp = wp->sem_wait.next)
				{
					HAK_ASSERT(hak, hak->sem_io_wait_count > 0);
					hak->sem_io_wait_count--;
					HAK_DEBUG1 (hak, "hak_pf_semaphore_group_remove_semaphore - lowered sem_io_wait_count to %zu\n", hak->sem_io_wait_count);
				}
			}
		}

		HAK_STACK_SETRET(hak, nargs, (hak_oop_t)sg);
		return HAK_PF_SUCCESS;
	}

	/* it doesn't belong to a group or belongs to a different group */
	hak_seterrbfmt(hak, HAK_EPERM, "not allowed to remove a semaphore from a non-owning group");
	return HAK_PF_FAILURE;
}

hak_pfrc_t hak_pf_semaphore_group_wait (hak_t* hak, hak_mod_t* mod, hak_ooi_t nargs)
{
	hak_oop_semaphore_group_t sg;
	hak_oop_t sem;


	sg = (hak_oop_semaphore_group_t)HAK_STACK_GETARG(hak, nargs, 0);
	if (!HAK_IS_SEMAPHORE_GROUP(hak, sg))
	{
		hak_seterrbfmt(hak, HAK_EINVAL, "parameter not semaphore group - %O", sg);
		return HAK_PF_FAILURE;
	}

	/* i must set the return value before calling await_semaphore_group().
	 * HAK_STACK_SETRETTORCV() manipulates the stack of the currently active
	 * process(hak->processor->active). hak->processor->active may become
	 * hak->nil_process if the current active process must get suspended.
	 * it is safer to set the return value of the calling method here.
	 * but the arguments and the receiver information will be lost from
	 * the stack from this moment on. */
	HAK_STACK_SETRET(hak, nargs, (hak_oop_t)sg);

	sem = await_semaphore_group(hak, sg);
	if (sem != hak->_nil)
	{
		/* there was a signaled semaphore. the active process won't get
		 * suspended. change the return value of the current process
		 * forcibly to the signaled semaphore */
		HAK_STACK_SETTOP(hak, sem);
	}

	/* the return value will get changed to an actual semaphore signaled
	 * when the semaphore is signaled. see signal_semaphore() */
	return HAK_PF_SUCCESS;
}

