#include <k/mem.h>
#include <k/core.h>
#include <k/def.h>
#include <k/type.h>
/* heapa.c - kmheapa() "heap alloc"
 * ~ lexi hale <lexi@hale.su>
 * kmheapa() allocates a pointer on the heap à la libc malloc()
 * see also: kmheapf() "heap free"
 */

/* arch specific headers */
#ifdef KFenv_posix
#	include <posix/posix.h>
#endif

#include <error_table.h>

/* we define all our platform functions here, whether or not
 * they're for the correct platform - only the ones that are
 * called by the preprocessed form of the code will actually
 * be linked, linker errors are our friend here! */

kmres kmheapa(sz len) {
	/* allocate an object on the heap and return
	 * a pointer, or NULL if the allocation failed. */
	union {
		void* raw;
		ubyte* byte;
		kmbox* header;
	} region;
	/* we need to allocate space for the
	 * header and for the actual object */
	sz region_size = sizeof(kmbox) + len;

	/* kmres reply is the struct that shall be returned by the
	 * function. if an error-condition occurs, the appropriate
	 * error code should be determined through system-specific
	 * means, the pointer value should be set to null, and the
	 * object should be returned immediately. if the object is
	 * not returned from within the system-specific section it
	 * will then be filled out with the appropriate values for
	 * a successful allocation and returned to the caller. */
	kmres reply;

#	ifdef KFenv_posix
		/* posix  APIs  - we've  got  it  easy. currently  for
		 * nonlinear  heap  allocation   kmheapa  simply  uses
		 * m(un)map  and lets  the kernel  worry about  it. it
		 * may  ultimately  be  worth replacing  this  with  a
		 * more sophisticated  implementation, most  likely an
		 * existing allocator  like jemalloc, though  i'm wary
		 * of including outside code  - it creates a licensing
		 * mess and  i'd prefer libk  to be AGPLv3  across the
		 * board.  possibility:  include  hooks  for  multiple
		 * allocators, allowing  the user to select  & link in
		 * her preferred allocator at compile time? */

		/* because munmap needs to be informed of the size of
		 * the region we are going to unmap, we need to store
		 * that information in the region that we are mapping.
		 * the user will receive an adjusted pointer that can
		 * be adjusted to point a field of type size_t that
		 * contains the size of the allocated space.*/

		k_platform_syscall_arg args[] = {
			null, region_size, 
			posix_prot_read | posix_prot_write,
			posix_flag_anonymous | posix_map_shared,
			-1, 0
		};

		/* impl note: while per manpage fd is "ignored" for
		 * MAP_ANONYMOUS,  "some implementations" require a
		 * value of -1, so we're setting it to that just to
		 * be safe. */

		struct k_platform_syscall_answer r = k_platform_syscall
			(k_platform_syscall_mmap, Kmsz(args), args);

		if (r.error == 0) region.byte = (ubyte*)r.ret; else {
			switch (r.error) {
				case k_platform_error_EAGAIN: reply.cond = kmcond_bad_lock; break;
				case k_platform_error_EINVAL: reply.cond = kmcond_bad_size; break;
				case k_platform_error_EMFILE: reply.cond = kmcond_too_many; break;
				case k_platform_error_ENOMEM: reply.cond = kmcond_no_room;  break;
				default: reply.cond = kmcond_fail_assert;
			}
			reply.raw = (void*)0;
			return reply;
		}
#	else
 	   Knoimpl(kmheapa,KVos);
#		error missing implementation
#	endif

	/* if the system-specific code has not returned early,
	 * we assume success and return the new pointer to the
	 * calling function along with a success code. */
	
	void* const object = (region.byte + sizeof (kmbox));

	region.header -> kind = kmkind_heap;
	region.header -> size = len;

	reply.cond = kmcond_ok;
	reply.raw = object;

	return reply;
}
