/*
    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"

#if defined(HAK_PROFILE_VM)
#include <sys/time.h>
#include <sys/resource.h> /* getrusage */
#endif

void* hak_allocbytes (hak_t* hak, hak_oow_t size)
{
	hak_gchdr_t* gch;
	hak_oow_t allocsize;
	int gc_called = 0;
#if defined(HAK_PROFILE_VM)
	struct rusage ru;
	hak_ntime_t rut;
#endif

#if defined(HAK_BUILD_DEBUG)
	if ((hak->option.trait & HAK_TRAIT_DEBUG_GC) && !(hak->option.trait & HAK_TRAIT_NOGC)) hak_gc(hak, 1);
#endif

#if defined(HAK_PROFILE_VM)
	getrusage(RUSAGE_SELF, &ru);
	HAK_INIT_NTIME (&rut,  ru.ru_utime.tv_sec, HAK_USEC_TO_NSEC(ru.ru_utime.tv_usec));
#endif

	allocsize = HAK_SIZEOF(*gch) + size;

	if (hak->gci.bsz >= hak->gci.threshold)
	{
		hak_gc(hak, 0);
		hak->gci.threshold = hak->gci.bsz + 100000; /* TODO: change this fomula */
		gc_called = 1;
	}

	if (hak->gci.lazy_sweep) hak_gc_ms_sweep_lazy(hak, allocsize);

	gch = (hak_gchdr_t*)hak_callocheapmem_noseterr(hak, hak->heap, allocsize);
	if (!gch)
	{
		if (HAK_UNLIKELY(hak->option.trait & HAK_TRAIT_NOGC)) goto calloc_heapmem_fail;
		if (gc_called) goto sweep_the_rest;

		hak_gc(hak, 0);
		if (hak->gci.lazy_sweep) hak_gc_ms_sweep_lazy(hak, allocsize);

		gch = (hak_gchdr_t*)hak_callocheapmem_noseterr(hak, hak->heap, allocsize);
		if (HAK_UNLIKELY(!gch))
		{
		sweep_the_rest:
			if (hak->gci.lazy_sweep)
			{
				hak_gc_ms_sweep_lazy(hak, HAK_TYPE_MAX(hak_oow_t)); /* sweep the rest */
				gch = (hak_gchdr_t*)hak_callocheapmem(hak, hak->heap, allocsize);
				if (HAK_UNLIKELY(!gch)) return HAK_NULL;
			}
			else
			{
			calloc_heapmem_fail:
				hak_seterrnum(hak, HAK_EOOMEM);
				return HAK_NULL;
			}
		}
	}

	if (hak->gci.lazy_sweep && hak->gci.ls.curr == hak->gci.b)
	{
		/* if the lazy sweeping point is at the beginning of the allocation block,
		 * hak->gc.ls.prev must get updated */
		HAK_ASSERT(hak, hak->gci.ls.prev == HAK_NULL);
		hak->gci.ls.prev = gch;
	}

	gch->next = hak->gci.b;
	hak->gci.b = gch;
	hak->gci.bsz += size;


#if defined(HAK_PROFILE_VM)
	getrusage(RUSAGE_SELF, &ru);
	HAK_SUB_NTIME_SNS (&rut, &rut, ru.ru_utime.tv_sec, HAK_USEC_TO_NSEC(ru.ru_utime.tv_usec));
	HAK_SUB_NTIME (&hak->gci.stat.alloc, &hak->gci.stat.alloc, &rut); /* do subtraction because rut is negative */
#endif
	return (hak_uint8_t*)(gch + 1);

}

static HAK_INLINE hak_oop_t alloc_oop_array (hak_t* hak, hak_oow_t size, int ngc)
{
	hak_oop_oop_t hdr;
	hak_oow_t nbytes, nbytes_aligned;

	nbytes = size * HAK_SIZEOF(hak_oop_t);

	/* this isn't really necessary since nbytes must be
	 * aligned already. */
	nbytes_aligned = HAK_ALIGN(nbytes, HAK_SIZEOF(hak_oop_t));

	if (HAK_UNLIKELY(ngc))
	{
		hdr = (hak_oop_oop_t)hak_callocmem(hak, HAK_SIZEOF(hak_obj_t) + nbytes_aligned);
	}
	else
	{
		/* making the number of bytes to allocate a multiple of
		 * HAK_SIZEOF(hak_oop_t) will guarantee the starting address
		 * of the allocated space to be an even number.
		 * see HAK_OOP_IS_NUMERIC() and HAK_OOP_IS_POINTER() */
		hdr = (hak_oop_oop_t)hak_allocbytes(hak, HAK_SIZEOF(hak_obj_t) + nbytes_aligned);
	}
	if (!hdr) return HAK_NULL;

	hdr->_flags = HAK_OBJ_MAKE_FLAGS(HAK_OBJ_TYPE_OOP, HAK_SIZEOF(hak_oop_t), 0, 0, 0, ngc, 0);
	HAK_OBJ_SET_SIZE (hdr, size);
	/*HAK_OBJ_SET_CLASS (hdr, hak->_nil);*/

	while (size > 0) hdr->slot[--size] = hak->_nil;

	return (hak_oop_t)hdr;
}


hak_oop_t hak_allocoopobj (hak_t* hak, hak_oow_t size)
{
	return alloc_oop_array(hak, size, 0);
}

hak_oop_t hak_allocoopobjwithtrailer (hak_t* hak, hak_oow_t size, const hak_oob_t* bptr, hak_oow_t blen)
{
	hak_oop_oop_t hdr;
	hak_oow_t nbytes, nbytes_aligned;
	hak_oow_t i;

	/* +1 for the trailer size of the hak_oow_t type */
	nbytes = (size + 1) * HAK_SIZEOF(hak_oop_t) + blen;
	nbytes_aligned = HAK_ALIGN(nbytes, HAK_SIZEOF(hak_oop_t));

	hdr = (hak_oop_oop_t)hak_allocbytes(hak, HAK_SIZEOF(hak_obj_t) + nbytes_aligned);
	if (HAK_UNLIKELY(!hdr)) return HAK_NULL;

	hdr->_flags = HAK_OBJ_MAKE_FLAGS(HAK_OBJ_TYPE_OOP, HAK_SIZEOF(hak_oop_t), 0, 0, 0, 0, 1);
	HAK_OBJ_SET_SIZE (hdr, size);
	/*HAK_OBJ_SET_CLASS (hdr, hak->_nil);*/

	for (i = 0; i < size; i++) hdr->slot[i] = hak->_nil;

	/* [NOTE] this is not converted to a SMOOI object */
	hdr->slot[size] = (hak_oop_t)blen;

	if (bptr) HAK_MEMCPY(&hdr->slot[size + 1], bptr, blen);
	else HAK_MEMSET(&hdr->slot[size + 1], 0, blen);

	return (hak_oop_t)hdr;
}

static HAK_INLINE hak_oop_t alloc_numeric_array (hak_t* hak, const void* ptr, hak_oow_t len, hak_obj_type_t type, hak_oow_t unit, int extra, int ngc)
{
	/* allocate a variable object */

	hak_oop_t hdr;
	hak_oow_t xbytes;
	hak_oow_t nbytes;
	hak_oow_t nbytes_aligned;
	hak_oow_t nbytes_total;

	xbytes = len * unit;
	/* 'extra' indicates an extra unit to append at the end.
	 * it's useful to store a string with a terminating null */
	nbytes = extra? xbytes + unit: xbytes;
	nbytes_aligned = HAK_ALIGN(nbytes, HAK_SIZEOF(hak_oop_t));
/* TODO: check overflow in size calculation*/

	nbytes_total = HAK_SIZEOF(hak_obj_t) + nbytes_aligned;
	/* making the number of bytes to allocate a multiple of
	 * HAK_SIZEOF(hak_oop_t) will guarantee the starting address
	 * of the allocated space to be an even number.
	 * see HAK_OOP_IS_NUMERIC() and HAK_OOP_IS_POINTER() */
	if (HAK_UNLIKELY(ngc))
		hdr = (hak_oop_t)hak_callocmem(hak, nbytes_total);
	else
		hdr = (hak_oop_t)hak_allocbytes(hak, nbytes_total);

	if (HAK_LIKELY(hdr))
	{
		hdr->_flags = HAK_OBJ_MAKE_FLAGS(type, unit, extra, 0, 0, ngc, 0);
		hdr->_size = len;
		HAK_OBJ_SET_SIZE (hdr, len);
		/*HAK_OBJ_SET_CLASS (hdr, hak->_nil);*/

		if (ptr)
		{
			/* copy data */
			HAK_MEMCPY(hdr + 1, ptr, xbytes);
			HAK_MEMSET((hak_uint8_t*)(hdr + 1) + xbytes, 0, nbytes_aligned - xbytes);
		}
		else
		{
			/* initialize with zeros when the string pointer is not given */
			HAK_MEMSET(hdr + 1, 0, nbytes_aligned);
		}
	}

	return hdr;
}

hak_oop_t hak_alloccharobj (hak_t* hak, const hak_ooch_t* ptr, hak_oow_t len)
{
	return alloc_numeric_array(hak, ptr, len, HAK_OBJ_TYPE_CHAR, HAK_SIZEOF(hak_ooch_t), 1, 0);
}

hak_oop_t hak_allocbyteobj (hak_t* hak, const hak_oob_t* ptr, hak_oow_t len)
{
	return alloc_numeric_array(hak, ptr, len, HAK_OBJ_TYPE_BYTE, HAK_SIZEOF(hak_oob_t), 0, 0);
}

hak_oop_t hak_allochalfwordobj (hak_t* hak, const hak_oohw_t* ptr, hak_oow_t len)
{
	return alloc_numeric_array(hak, ptr, len, HAK_OBJ_TYPE_HALFWORD, HAK_SIZEOF(hak_oohw_t), 0, 0);
}

hak_oop_t hak_allocwordobj (hak_t* hak, const hak_oow_t* ptr, hak_oow_t len)
{
	return alloc_numeric_array(hak, ptr, len, HAK_OBJ_TYPE_WORD, HAK_SIZEOF(hak_oow_t), 0, 0);
}

/* ------------------------------------------------------------------------ *
 * COMMON OBJECTS
 * ------------------------------------------------------------------------ */

hak_oop_t hak_hatchundef (hak_t* hak)
{
	/* create the undef object for bootstrapping.
	 * this function doesn't set the class field */

	hak_oop_t v;
	v = hak_allocoopobj(hak, 0);
	if (HAK_UNLIKELY(!v))
	{
		const hak_ooch_t* orgmsg = hak_backuperrmsg(hak);
		hak_seterrbfmt(hak, HAK_ERRNUM(hak), "unable to make undef - %js", orgmsg);
	}
	else
	{
		HAK_OBJ_SET_FLAGS_KERNEL(v, 1);
	}
	return v;
}

hak_oop_t hak_hatchnil (hak_t* hak)
{
	/* create the nil object for bootstrapping.
	 * this function doesn't set the class field */

	hak_oop_t v;
	v = hak_allocoopobj(hak, 0);
	if (HAK_UNLIKELY(!v))
	{
		const hak_ooch_t* orgmsg = hak_backuperrmsg(hak);
		hak_seterrbfmt(hak, HAK_ERRNUM(hak), "unable to make nil - %js", orgmsg);
	}
	else
	{
		HAK_OBJ_SET_FLAGS_KERNEL(v, 1);
	}
	return v;
}

hak_oop_t hak_makecons (hak_t* hak, hak_oop_t car, hak_oop_t cdr)
{
/* TODO: use hak_instantiate() */
#if 0
	hak_oop_cons_t cons;

	hak_pushvolat(hak, &car);
	hak_pushvolat(hak, &cdr);

	cons = (hak_oop_cons_t)hak_allocoopobj(hak, HAK_BRAND_CONS, 2);
	if (HAK_LIKELY(cons))
	{
		cons->car = car;
		cons->cdr = cdr;
		HAK_OBJ_SET_CLASS (cons, (hak_oop_t)hak->c_cons);
	}

	hak_popvolats(hak, 2);

	return (hak_oop_t)cons;
#else
	hak_oop_cons_t v;
	hak_pushvolat(hak, &car);
	hak_pushvolat(hak, &cdr);
	v = (hak_oop_cons_t)hak_instantiate(hak, hak->c_cons, HAK_NULL, 0);
	hak_popvolats(hak, 2);
	if (HAK_UNLIKELY(!v))
	{
		const hak_ooch_t* orgmsg = hak_backuperrmsg(hak);
		hak_seterrbfmt(hak, HAK_ERRNUM(hak), "unable to instantiate %O - %js", hak->c_cons->name, orgmsg);
	}
	else
	{
		v->car = car;
		v->cdr = cdr;
	}
	return (hak_oop_t)v;
#endif
}

hak_oop_t hak_makearray (hak_t* hak, hak_oow_t len)
{
#if 0
	hak_oop_t v;
	v = hak_allocoopobj(hak, HAK_BRAND_ARRAY, size);
	if (HAK_LIKELY(v)) HAK_OBJ_SET_CLASS (v, (hak_oop_t)hak->c_array);
	return v;
#else
	hak_oop_t v;
	v = hak_instantiate(hak, hak->c_array, HAK_NULL, len);
	if (HAK_UNLIKELY(!v))
	{
		const hak_ooch_t* orgmsg = hak_backuperrmsg(hak);
		hak_seterrbfmt(hak, HAK_ERRNUM(hak),
			"unable to instantiate %O - %js", hak->c_array->name, orgmsg);
	}
	return v;
#endif
}

hak_oop_t hak_makechararray (hak_t* hak, const hak_ooch_t* ptr, hak_oow_t len)
{
	hak_oop_t v;
	v = hak_instantiate(hak, hak->c_character_array, ptr, len);
	if (HAK_UNLIKELY(!v))
	{
		const hak_ooch_t* orgmsg = hak_backuperrmsg(hak);
		hak_seterrbfmt(hak, HAK_ERRNUM(hak),
			"unable to instantiate %O - %js", hak->c_character_array->name, orgmsg);
	}
	return v;
}

hak_oop_t hak_makebytearray (hak_t* hak, const hak_oob_t* ptr, hak_oow_t len)
{
#if 0
	hak_oop_t v;
	v = hak_allocbyteobj(hak, HAK_BRAND_BYTE_ARRAY, ptr, size);
	if (HAK_LIKELY(v)) HAK_OBJ_SET_CLASS (v, (hak_oop_t)hak->c_byte_array);
	return v;
#else
	hak_oop_t v;
	v = hak_instantiate(hak, hak->c_byte_array, ptr, len);
	if (HAK_UNLIKELY(!v))
	{
		const hak_ooch_t* orgmsg = hak_backuperrmsg(hak);
		hak_seterrbfmt(hak, HAK_ERRNUM(hak),
			"unable to instantiate %O - %js", hak->c_byte_array->name, orgmsg);
	}
	return v;
#endif
}

hak_oop_t hak_makebytestringwithbytes (hak_t* hak, const hak_oob_t* ptr, hak_oow_t len)
{
	hak_oop_byte_t v;
	v = (hak_oop_byte_t)hak_instantiate(hak, hak->c_byte_string, ptr, len);
	if (HAK_UNLIKELY(!v))
	{
		const hak_ooch_t* orgmsg = hak_backuperrmsg(hak);
		hak_seterrbfmt(hak, HAK_ERRNUM(hak),
			"unable to instantiate %O with bytes - %js", hak->c_byte_string->name, orgmsg);
	}
	return (hak_oop_t)v;
}

hak_oop_t hak_makebytestring (hak_t* hak, const hak_ooch_t* ptr, hak_oow_t len)
{
	/* a byte string is a byte array with an extra null at the back.
	 * the input to this function, however, is the pointer to hak_ooch_t data
	 * because this function is mainly used to convert a token to a byte string.
	 * the token in the compiler is stored as a hak_ooch_t string. */

	hak_oop_byte_t v;

	v = (hak_oop_byte_t)hak_instantiate(hak, hak->c_byte_string, HAK_NULL, len);
	if (HAK_UNLIKELY(!v))
	{
		const hak_ooch_t* orgmsg = hak_backuperrmsg(hak);
		hak_seterrbfmt(hak, HAK_ERRNUM(hak),
			"unable to instantiate %O - %js", hak->c_byte_string->name, orgmsg);
	}
	else
	{
		hak_oow_t i;
		hak_oob_t b;
		for (i = 0; i < len; i++)
		{
			b = ptr[i] & 0xFF;
			HAK_OBJ_SET_BYTE_VAL(v, i, b);
		}
	}

	return (hak_oop_t)v;
}

hak_oop_t hak_makestring (hak_t* hak, const hak_ooch_t* ptr, hak_oow_t len)
{
	hak_oop_t v;
	v = hak_instantiate(hak, hak->c_string, ptr, len);
	if (HAK_UNLIKELY(!v))
	{
		const hak_ooch_t* orgmsg = hak_backuperrmsg(hak);
		hak_seterrbfmt(hak, HAK_ERRNUM(hak),
			"unable to instantiate %O - %js", hak->c_string->name, orgmsg);
	}
	return v;
}

hak_oop_t hak_makestringwithuchars (hak_t* hak, const hak_uch_t* ptr, hak_oow_t len)
{
	/* you must provide the payload when calling this variant. it can't figure out
	 * the actual number of hak_ooch_t characters */
	if (!ptr)
	{
		hak_seterrbfmt(hak, HAK_EINVAL,
			"unable to instantiate %O - null payload", hak->c_string->name);
		return HAK_NULL;
	}

#if defined(HAK_OOCH_IS_UCH)
	return hak_makestring(hak, ptr, len);
#else
	hak_oow_t xlen;
	hak_ooch_t* xptr;

	xptr = hak_duputooochars(hak, ptr, len, &xlen);
	if (HAK_UNLIKELY(!xptr))
	{
		const hak_ooch_t* orgmsg = hak_backuperrmsg(hak);
		hak_seterrbfmt(hak, HAK_ERRNUM(hak),
			"unable to instantiate %O - %js", hak->c_string->name, orgmsg);
		return HAK_NULL;
	}

	return hak_makestring(hak, xptr, xlen);
#endif
}

hak_oop_t hak_makestringwithbchars (hak_t* hak, const hak_bch_t* ptr, hak_oow_t len)
{
	/* you must provide the payload when calling this variant. it can't figure out
	 * the actual number of hak_ooch_t characters */
	if (!ptr)
	{
		hak_seterrbfmt(hak, HAK_EINVAL,
			"unable to instantiate %O - null payload", hak->c_string->name);
		return HAK_NULL;
	}

#if defined(HAK_OOCH_IS_UCH)
	hak_oow_t xlen;
	hak_ooch_t* xptr;

	xptr = hak_dupbtooochars(hak, ptr, len, &xlen);
	if (HAK_UNLIKELY(!xptr))
	{
		const hak_ooch_t* orgmsg = hak_backuperrmsg(hak);
		hak_seterrbfmt(hak, HAK_ERRNUM(hak),
			"unable to instantiate %O - %js", hak->c_string->name, orgmsg);
		return HAK_NULL;
	}

	return hak_makestring(hak, xptr, xlen);
#else
	return hak_makestring(hak, ptr, len);
#endif
}

hak_oop_t hak_makefpdec (hak_t* hak, hak_oop_t value, hak_ooi_t scale)
{
	hak_oop_fpdec_t f;

	HAK_ASSERT(hak, hak_isint(hak, value));

	if (scale <= 0) return value; /* if scale is 0 or less, return the value as it it */

	if (scale > HAK_SMOOI_MAX)
	{
		hak_seterrbfmt(hak, HAK_EINVAL, "fpdec scale too large - %zd", scale);
		return HAK_NULL;
	}

	hak_pushvolat(hak, &value);
	f = (hak_oop_fpdec_t)hak_instantiate(hak, hak->c_fixed_point_decimal, HAK_NULL, 0);
	hak_popvolat(hak);

	if (HAK_UNLIKELY(!f))
	{
		const hak_ooch_t* orgmsg = hak_backuperrmsg(hak);
		hak_seterrbfmt(
			hak, HAK_ERRNUM(hak), "unable to instantiate %O - %js",
			hak->c_fixed_point_decimal->name, orgmsg);
	}
	else
	{
		f->value = value;
		f->scale = HAK_SMOOI_TO_OOP(scale);
	}

	return (hak_oop_t)f;
}

hak_oop_t hak_makeclass (hak_t* hak, hak_oop_t class_name, hak_oop_t superclass, hak_ooi_t spec, hak_ooi_t selfspec, hak_oop_t ivars_str, hak_oop_t cvars_str)
{
	hak_oop_class_t c;

	hak_pushvolat(hak, &class_name);
	hak_pushvolat(hak, &superclass);
	hak_pushvolat(hak, &ivars_str);
	hak_pushvolat(hak, &cvars_str);
	c = (hak_oop_class_t)hak_instantiate(hak, hak->c_class, HAK_NULL, HAK_CLASS_SELFSPEC_CLASSVARS(selfspec));
	hak_popvolats(hak, 4);
	if (HAK_UNLIKELY(!c))
	{
		const hak_ooch_t* orgmsg = hak_backuperrmsg(hak);
		hak_seterrbfmt(hak, HAK_ERRNUM(hak),
			"unable to instantiate class %O - %js", class_name, orgmsg);
	}
	else
	{
		hak_ooi_t nivars_super;

		if (!HAK_IS_NIL(hak, superclass))
		{
			hak_ooi_t superspec;
			superspec = HAK_OOP_TO_SMOOI(((hak_oop_class_t)superclass)->spec);
			nivars_super = HAK_OOP_TO_SMOOI(((hak_oop_class_t)superclass)->nivars_super) + HAK_CLASS_SPEC_NAMED_INSTVARS(superspec);
		}
		else
		{
			nivars_super = 0;
		}

		c->spec = HAK_SMOOI_TO_OOP(spec);
		c->selfspec = HAK_SMOOI_TO_OOP(selfspec);
		c->name = class_name;
		c->superclass = superclass;
		c->nivars_super = HAK_SMOOI_TO_OOP(nivars_super);
		c->ibrand = HAK_SMOOI_TO_OOP(HAK_BRAND_INSTANCE); /* TODO: really need ibrand??? */

		/* TODO: remember ivars_str and vars_str? */
		/* duplicate ivars_str and cvars_str and set it to c->ivarnames and c->cvarnames???? */
	}

	return (hak_oop_t)c;
}

struct decoded_spec_t
{
	hak_obj_type_t type;
	hak_oow_t alloclen;
	int flexi;
};
typedef struct decoded_spec_t decoded_spec_t;

static HAK_INLINE int decode_spec (hak_t* hak, hak_oop_class_t _class, hak_oow_t num_flexi_fields, decoded_spec_t* dspec)
{
	hak_oow_t spec;
	hak_oow_t num_fixed_fields;
	hak_obj_type_t indexed_type;

	HAK_ASSERT(hak, HAK_OOP_IS_POINTER(_class));
	HAK_ASSERT(hak, HAK_CLASSOF(hak, _class) == (hak_oop_t)hak->c_class);

	HAK_ASSERT(hak, HAK_OOP_IS_SMOOI(_class->spec));
	spec = HAK_OOP_TO_SMOOI(_class->spec);

	num_fixed_fields = HAK_CLASS_SPEC_NAMED_INSTVARS(spec);
	HAK_ASSERT(hak, num_fixed_fields <= HAK_MAX_NAMED_INSTVARS);

	if (HAK_CLASS_SPEC_IS_INDEXED(spec))
	{
		indexed_type = (hak_obj_type_t)HAK_CLASS_SPEC_INDEXED_TYPE(spec);

		/* the number of the fixed fields for a non-pointer object are supported.
		 * the fixed fields of a pointer object holds named instance variables
		 * and a non-pointer object is facilitated with the fixed fields of the size
		 * specified in the class description like #byte(5), #word(10).
		 *
		 * when it comes to spec decoding, there is no difference between a pointer
		 * object and a non-pointer object */

		if (num_flexi_fields > HAK_MAX_INDEXED_INSTVARS(num_fixed_fields))
		{
			hak_seterrbfmt(hak, HAK_EINVAL, "number of flexi-fields(%zu) too big for class %O", num_flexi_fields, _class);
			return -1;
		}
	}
	else
	{
		/* named instance variables only. treat it as if it is an
		 * indexable class with no variable data */

		/* for an object composed of non-oop fields,
		 * the field can be accessed using a instance variable name.
		 * the instructions for instance variable access must cater for this.
		 * for example, the Primitive class is HAK_OBJ_TYPE_WORD and not variable */
		/*indexed_type = HAK_OBJ_TYPE_OOP; <- no more fixed to OOP fields only. */
		indexed_type = (hak_obj_type_t)HAK_CLASS_SPEC_INDEXED_TYPE(spec);

		if (num_flexi_fields > 0)
		{
			hak_seterrbfmt(hak, HAK_EPERM, "flexi-fields(%zu) disallowed for class %O", num_flexi_fields, _class);
			return -1;
		}
	}

	HAK_ASSERT(hak, num_fixed_fields + num_flexi_fields <= HAK_OBJ_SIZE_MAX);
	dspec->flexi = !!HAK_CLASS_SPEC_IS_INDEXED(spec);
	dspec->type = indexed_type;
	dspec->alloclen = num_fixed_fields + num_flexi_fields + HAK_OOP_TO_SMOOI(_class->nivars_super);
	return 0;
}

hak_oop_t hak_instantiate (hak_t* hak, hak_oop_class_t _class, const void* vptr, hak_oow_t vlen)
{
	hak_oop_t oop;
	decoded_spec_t dspec;
	hak_oow_t tmp_count = 0;

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

	if (decode_spec(hak, _class, vlen, &dspec) <= -1) return HAK_NULL;

	hak_pushvolat(hak, (hak_oop_t*)&_class); tmp_count++;

	switch (dspec.type)
	{
		case HAK_OBJ_TYPE_OOP:
			/* both the fixed part(named instance variables) and
			 * the variable part(indexed instance variables) are allowed. */
			oop = hak_allocoopobj(hak, dspec.alloclen);
			if (HAK_LIKELY(oop))
			{
		#if 0
				/* initialize named instance variables with default values */
				if (_class->initv[0] != hak->_nil)
				{
					hak_oow_t i = HAK_OBJ_GET_SIZE(_class->initv[0]);

					/* [NOTE] i don't deep-copy initial values.
					 *   if you change the contents of compound values like arrays,
					 *   it affects subsequent instantiation of the class.
					 *   it's important that the compiler should mark compound initial
					 *   values read-only. */
					while (i > 0)
					{
						--i;
						HAK_OBJ_SET_OOP_VAL (oop, i, HAK_OBJ_GET_OOP_VAL(_class->initv[0], i));
					}
				}
		#endif
			}
			HAK_ASSERT(hak, vptr == HAK_NULL);
			/*
			This function is not GC-safe. so i don't want to initialize
			the payload of a pointer object. The caller can call this
			function and initialize payloads then.
			if (oop && vptr && vlen > 0)
			{
				hak_oop_oop_t hdr = (hak_oop_oop_t)oop;
				HAK_MEMCPY(&hdr->slot[named_ivar], vptr, vlen * HAK_SIZEOF(hak_oop_t));
			}

			For the above code to work, it should protect the elements of
			the vptr array with hak_pushvolat(). So it might be better
			to disallow a non-NULL vptr when indexed_type is OOP. See
			the assertion above this comment block.
			*/
			break;

		case HAK_OBJ_TYPE_CHAR:
			oop = hak_alloccharobj(hak, (const hak_ooch_t*)vptr, dspec.alloclen);
			break;

		case HAK_OBJ_TYPE_BYTE:
			oop = hak_allocbyteobj(hak, (const hak_oob_t*)vptr, dspec.alloclen);
			break;

		case HAK_OBJ_TYPE_HALFWORD:
			oop = hak_allochalfwordobj(hak, (const hak_oohw_t*)vptr, dspec.alloclen);
			break;

		case HAK_OBJ_TYPE_WORD:
			oop = hak_allocwordobj(hak, (const hak_oow_t*)vptr, dspec.alloclen);
			break;

		/* TODO: more types... HAK_OBJ_TYPE_INT... HAK_OBJ_TYPE_FLOAT, HAK_OBJ_TYPE_UINT16, etc*/
		default:
			hak_seterrnum(hak, HAK_EINTERN);
			oop = HAK_NULL;
			break;
	}

	if (HAK_LIKELY(oop))
	{
		hak_ooi_t spec;
		HAK_OBJ_SET_CLASS (oop, (hak_oop_t)_class);
		spec = HAK_OOP_TO_SMOOI(_class->spec);
		if (HAK_CLASS_SPEC_IS_IMMUTABLE(spec)) HAK_OBJ_SET_FLAGS_RDONLY (oop, 1);
	#if 0 /* TODO: revive this part */
		if (HAK_CLASS_SPEC_IS_UNCOPYABLE(spec)) HAK_OBJ_SET_FLAGS_UNCOPYABLE (oop, 1);
	#endif
		HAK_OBJ_SET_FLAGS_FLEXI(oop, dspec.flexi);
	}
	hak_popvolats(hak, tmp_count);
	return oop;
}

hak_oop_t hak_instantiatewithtrailer (hak_t* hak, hak_oop_class_t _class, hak_oow_t vlen, const hak_oob_t* trptr, hak_oow_t trlen)
{
	hak_oop_t oop;
	decoded_spec_t dspec;
	hak_oow_t tmp_count = 0;

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

	if (decode_spec(hak, _class, vlen, &dspec) <= -1) return HAK_NULL;

	hak_pushvolat(hak, (hak_oop_t*)&_class); tmp_count++;

	switch (dspec.type)
	{
		case HAK_OBJ_TYPE_OOP:
			oop = hak_allocoopobjwithtrailer(hak, dspec.alloclen, trptr, trlen);
			if (HAK_LIKELY(oop))
			{
				/* initialize named instance variables with default values */
			#if 0 /* TODO: revive this part */
				if (_class->initv[0] != hak->_nil)
				{
					hak_oow_t i = HAK_OBJ_GET_SIZE(_class->initv[0]);

					/* [NOTE] i don't deep-copy initial values.
					 *   if you change the contents of compound values like arrays,
					 *   it affects subsequent instantiation of the class.
					 *   it's important that the compiler should mark compound initial
					 *   values read-only. */
					while (i > 0)
					{
						--i;
						HAK_STORE_OOP(hak, HAK_OBJ_GET_OOP_PTR(oop, i), HAK_OBJ_GET_OOP_VAL(_class->initv[0], i));
					}
				}
			#endif
			}

			break;

		default:
		#if 0
			HAK_DEBUG3 (hak, "Not allowed to instantiate a non-pointer object of the %.*js class with trailer %zu\n",
				HAK_OBJ_GET_SIZE(_class->name),
				HAK_OBJ_GET_CHAR_SLOT(_class->name),
				trlen);
		#endif

			hak_seterrnum(hak, HAK_EPERM);
			oop = HAK_NULL;
			break;
	}

	if (HAK_LIKELY(oop))
	{
		hak_ooi_t spec;
		HAK_OBJ_SET_CLASS (oop, (hak_oop_t)_class);
		spec = HAK_OOP_TO_SMOOI(_class->spec);
		if (HAK_CLASS_SPEC_IS_IMMUTABLE(spec)) HAK_OBJ_SET_FLAGS_RDONLY (oop, 1);
	#if 0 /* TODO: revive this part */
		/* the object with trailer is to to uncopyable in hak_allocoopobjwithtrailer() so no need to check/set it again here
		if (HAK_CLASS_SPEC_IS_UNCOPYABLE(spec)) HAK_OBJ_SET_FLAGS_UNCOPYABLE (oop, 1);
		*/
	#endif
		HAK_OBJ_SET_FLAGS_FLEXI(oop, dspec.flexi);
	}
	hak_popvolats(hak, tmp_count);
	return oop;
}

/* ------------------------------------------------------------------------ *
 * NGC HANDLING
 * ------------------------------------------------------------------------ */

void hak_freengcobj (hak_t* hak, hak_oop_t obj)
{
	if (HAK_OOP_IS_POINTER(obj) && HAK_OBJ_GET_FLAGS_NGC(obj)) hak_freemem(hak, obj);
}

hak_oop_t hak_makengcbytearray (hak_t* hak, const hak_oob_t* ptr, hak_oow_t len)
{
	return alloc_numeric_array(hak, ptr, len, HAK_OBJ_TYPE_BYTE, HAK_SIZEOF(hak_oob_t), 0, 1);
}

hak_oop_t hak_remakengcbytearray (hak_t* hak, hak_oop_t obj, hak_oow_t newsize)
{
	hak_oop_t tmp;

	HAK_ASSERT(hak, !obj || (HAK_OOP_IS_POINTER(obj) && HAK_OBJ_GET_FLAGS_NGC(obj)));

	/* no hak_pushvolat() is needed because 'obj' is a non-GC object. */
	/* TODO: improve this by using realloc */

	tmp = hak_makengcbytearray(hak, HAK_NULL, newsize);
	if (HAK_LIKELY(tmp))
	{
		if (obj)
		{
			hak_oow_t cpsize;
			cpsize =  (newsize > HAK_OBJ_GET_SIZE(obj))? HAK_OBJ_GET_SIZE(obj): newsize;
			HAK_MEMCPY(((hak_oop_byte_t)tmp)->slot, ((hak_oop_byte_t)obj)->slot, cpsize * HAK_SIZEOF(hak_oob_t));
		}
		hak_freengcobj(hak, obj);
	}
	return tmp;
}

hak_oop_t hak_makengcarray (hak_t* hak, hak_oow_t len)
{
	return alloc_numeric_array(hak, HAK_NULL, len, HAK_OBJ_TYPE_OOP, HAK_SIZEOF(hak_oop_t), 0, 1);
}

hak_oop_t hak_remakengcarray (hak_t* hak, hak_oop_t obj, hak_oow_t newsize)
{
	hak_oop_t tmp;

	HAK_ASSERT(hak, !obj || (HAK_OOP_IS_POINTER(obj) && HAK_OBJ_GET_FLAGS_NGC(obj)));

	/* no hak_pushvolat() is needed because 'obj' is a non-GC object. */
	/* TODO: improve this by using realloc */

	tmp = hak_makengcarray(hak, newsize);
	if (HAK_LIKELY(tmp))
	{
		if (obj)
		{
			hak_oow_t cpsize;
			cpsize =  (newsize > HAK_OBJ_GET_SIZE(obj))? HAK_OBJ_GET_SIZE(obj): newsize;
			HAK_MEMCPY(((hak_oop_oop_t)tmp)->slot, ((hak_oop_oop_t)obj)->slot, cpsize * HAK_SIZEOF(hak_oop_t));
		}
		hak_freengcobj(hak, obj);
	}
	return tmp;
}

/* ------------------------------------------------------------------------ *
 * CONS
 * ------------------------------------------------------------------------ */
hak_oow_t hak_countcons (hak_t* hak, hak_oop_t cons)
{
	/* this function ignores the last cdr */
	hak_oow_t count = 1;

	HAK_ASSERT(hak, HAK_IS_CONS(hak, cons));
	do
	{
		cons = HAK_CONS_CDR(cons);
		if (!HAK_IS_CONS(hak, cons)) break;
		count++;
	}
	while (1);

	return count;
}

hak_oop_t hak_getlastconscdr (hak_t* hak, hak_oop_t cons)
{
	HAK_ASSERT(hak, HAK_IS_CONS(hak, cons));
	do
	{
		cons = HAK_CONS_CDR(cons);
		if (!HAK_IS_CONS(hak, cons)) break;
	}
	while (1);

	return cons;
}

hak_oop_t hak_reversecons (hak_t* hak, hak_oop_t cons)
{
	hak_oop_t ptr, prev, next;

	/* Note: The non-nil cdr in the last cons cell gets lost.
	 *  e.g.) Reversing (1 2 3 . 4) results in (3 2 1) */

	HAK_ASSERT(hak, HAK_IS_CONS(hak, cons));

	prev = hak->_nil;
	ptr = cons;

	do
	{
		next = HAK_CONS_CDR(ptr);
		HAK_CONS_CDR(ptr) = prev;
		prev = ptr;
		if (!HAK_IS_CONS(hak, next)) break;
		ptr = next;
	}
	while (1);

	return ptr;
}


/* ------------------------------------------------------------------------ *
 * OBJECT HASHING
 * ------------------------------------------------------------------------ */
int hak_hashobj (hak_t* hak, hak_oop_t obj, hak_oow_t* xhv)
{
	hak_oow_t hv;

	if (obj == hak->_nil)
	{
		*xhv = 0;
		return 0;
	}
	else if (obj == hak->_true)
	{
		*xhv = 1;
		return 0;
	}
	else if (obj == hak->_false)
	{
		*xhv = 2;
		return 0;
	}

	switch (HAK_OOP_GET_TAG(obj))
	{
		case HAK_OOP_TAG_SMOOI:
			hv = HAK_OOP_TO_SMOOI(obj);
			break;

/*
		case HAK_OOP_TAG_SMPTR:
			hv = (hak_oow_t)HAK_OOP_TO_SMPTR(obj);
			break;
*/

		case HAK_OOP_TAG_CHAR:
			hv = HAK_OOP_TO_CHAR(obj);
			break;

/*
		case HAK_OOP_TAG_ERROR:
			hv = HAK_OOP_TO_ERROR(obj);
			break;
*/

		default:
		{
			int type;

			HAK_ASSERT(hak, HAK_OOP_IS_POINTER(obj));
			type = HAK_OBJ_GET_FLAGS_TYPE(obj);
			switch (type)
			{
				case HAK_OBJ_TYPE_BYTE:
					hv = hak_hash_bytes(((hak_oop_byte_t)obj)->slot, HAK_OBJ_GET_SIZE(obj));
					break;

				case HAK_OBJ_TYPE_CHAR:
					hv = hak_hash_oochars(((hak_oop_char_t)obj)->slot, HAK_OBJ_GET_SIZE(obj));
					break;

				case HAK_OBJ_TYPE_HALFWORD:
					hv = hak_hash_halfwords(((hak_oop_halfword_t)obj)->slot, HAK_OBJ_GET_SIZE(obj));
					break;

				case HAK_OBJ_TYPE_WORD:
					hv = hak_hash_words(((hak_oop_word_t)obj)->slot, HAK_OBJ_GET_SIZE(obj));
					break;

				default:
					/* HAK_OBJ_TYPE_OOP, ... */
					hak_seterrbfmt(hak, HAK_ENOIMPL, "no builtin hash implemented for %O", obj); /* TODO: better error code? */
					return -1;
			}
			break;
		}
	}

	/* i assume that hak_hashxxx() functions limits the return value to fall
	 * between 0 and HAK_SMOOI_MAX inclusive */
	HAK_ASSERT(hak, hv >= 0 && hv <= HAK_SMOOI_MAX);
	*xhv = hv;
	return 0;
}

/* ------------------------------------------------------------------------ *
 * OBJECT EQUALITY
 * ------------------------------------------------------------------------ */
int hak_equalobjs (hak_t* hak, hak_oop_t rcv, hak_oop_t arg)
{
	int rtag;

	if (rcv == arg) return 1; /* identical. so equal */

	rtag = HAK_OOP_GET_TAG(rcv);
	if (rtag != HAK_OOP_GET_TAG(arg)) return 0;

	switch (rtag)
	{
		case HAK_OOP_TAG_SMOOI:
			return HAK_OOP_TO_SMOOI(rcv) == HAK_OOP_TO_SMOOI(arg)? 1: 0;

		case HAK_OOP_TAG_SMPTR:
			return HAK_OOP_TO_SMPTR(rcv) == HAK_OOP_TO_SMPTR(arg)? 1: 0;

		case HAK_OOP_TAG_CHAR:
			return HAK_OOP_TO_CHAR(rcv) == HAK_OOP_TO_CHAR(arg)? 1: 0;

		case HAK_OOP_TAG_ERROR:
			return HAK_OOP_TO_ERROR(rcv) == HAK_OOP_TO_ERROR(arg)? 1: 0;

		default:
		{
			HAK_ASSERT(hak, HAK_OOP_IS_POINTER(rcv));

			if (HAK_OBJ_GET_CLASS(rcv) != HAK_OBJ_GET_CLASS(arg)) return 0; /* different class, not equal */
			HAK_ASSERT(hak, HAK_OBJ_GET_FLAGS_TYPE(rcv) == HAK_OBJ_GET_FLAGS_TYPE(arg));

			if (HAK_OBJ_GET_SIZE(rcv) != HAK_OBJ_GET_SIZE(arg)) return 0; /* different size, not equal */

			switch (HAK_OBJ_GET_FLAGS_TYPE(rcv))
			{
				case HAK_OBJ_TYPE_BYTE:
				case HAK_OBJ_TYPE_CHAR:
				case HAK_OBJ_TYPE_HALFWORD:
				case HAK_OBJ_TYPE_WORD:
					return (HAK_MEMCMP(HAK_OBJ_GET_BYTE_SLOT(rcv), HAK_OBJ_GET_BYTE_SLOT(arg), HAK_BYTESOF(hak,rcv)) == 0)? 1: 0;

				default:
				{
					hak_oow_t i, size;

					if (rcv == hak->_nil) return arg == hak->_nil? 1: 0;
					if (rcv == hak->_true) return arg == hak->_true? 1: 0;
					if (rcv == hak->_false) return arg == hak->_false? 1: 0;

					/* HAK_OBJ_TYPE_OOP, ... */
					HAK_ASSERT(hak, HAK_OBJ_GET_FLAGS_TYPE(rcv) == HAK_OBJ_TYPE_OOP);

				#if 0
					hak_seterrbfmt(hak, HAK_ENOIMPL, "no builtin comparison implemented for %O and %O", rcv, arg); /* TODO: better error code */
					return -1;
				#else

					if (HAK_IS_PROCESS(hak,rcv))
					{
						/* the stack in a process object doesn't need to be
						 * scanned in full. the slots above the stack pointer
						 * are garbages. */
						size = HAK_PROCESS_NAMED_INSTVARS +
							  HAK_OOP_TO_SMOOI(((hak_oop_process_t)rcv)->sp) + 1;
						HAK_ASSERT(hak, size <= HAK_OBJ_GET_SIZE(rcv));
					}
					else
					{
						size = HAK_OBJ_GET_SIZE(rcv);
					}
					for (i = 0; i < size; i++)
					{
						int n;
						/* TODO: remove recursion */
						/* NOTE: even if the object implements the equality method,
						 * this primitive method doesn't honor it. */
						n = hak_equalobjs(hak, ((hak_oop_oop_t)rcv)->slot[i], ((hak_oop_oop_t)arg)->slot[i]);
						if (n <= 0) return n;
					}

					/* the default implementation doesn't take the trailer space into account */
					return 1;
				#endif
				}
			}
		}
	}
}
