/*
 *  Example memory allocator with machine parseable logging.
 *
 *  Also sizes for reallocs and frees are logged so that the memory
 *  behavior can be essentially replayed to accurately determine e.g.
 *  optimal pool sizes for a pooled allocator.
 *
 *  Allocation structure:
 *
 *    [ alloc_hdr | user area ]
 *
 *     ^           ^
 *     |           `--- pointer returned to Duktape
 *     `--- underlying malloc ptr
 */

#include "duktape.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

#define  ALLOC_LOG_FILE  "/tmp/duk-alloc-log.txt"

typedef struct {
	/* The double value in the union is there to ensure alignment is
	 * good for IEEE doubles too.  In many 32-bit environments 4 bytes
	 * would be sufficiently aligned and the double value is unnecessary.
	 */
	union {
		size_t sz;
		double d;
	} u;
} alloc_hdr;

static FILE *log_file = NULL;

static void write_log(const char *fmt, ...) {
	va_list ap;

	if (!log_file) {
		log_file = fopen(ALLOC_LOG_FILE, "wb");
		if (!log_file) {
			return;
		}
	}

	va_start(ap, fmt);
	vfprintf(log_file, fmt, ap);
	va_end(ap);
}

void *duk_alloc_logging(void *udata, duk_size_t size) {
	alloc_hdr *hdr;
	void *ret;

	(void) udata;  /* Suppress warning. */

	if (size == 0) {
		write_log("A NULL %ld\n", (long) size);
		return NULL;
	}

	hdr = (alloc_hdr *) malloc(size + sizeof(alloc_hdr));
	if (!hdr) {
		write_log("A FAIL %ld\n", (long) size);
		return NULL;
	}
	hdr->u.sz = size;
	ret = (void *) (hdr + 1);
	write_log("A %p %ld\n", ret, (long) size);
	return ret;
}

void *duk_realloc_logging(void *udata, void *ptr, duk_size_t size) {
	alloc_hdr *hdr;
	size_t old_size;
	void *t;
	void *ret;

	(void) udata;  /* Suppress warning. */

	/* Handle the ptr-NULL vs. size-zero cases explicitly to minimize
	 * platform assumptions.  You can get away with much less in specific
	 * well-behaving environments.
	 */

	if (ptr) {
		hdr = (alloc_hdr *) (void *) ((unsigned char *) ptr - sizeof(alloc_hdr));
		old_size = hdr->u.sz;

		if (size == 0) {
			write_log("R %p %ld NULL 0\n", ptr, (long) old_size);
			free((void *) hdr);
			return NULL;
		} else {
			t = realloc((void *) hdr, size + sizeof(alloc_hdr));
			if (!t) {
				write_log("R %p %ld FAIL %ld\n", ptr, (long) old_size, (long) size);
				return NULL;
			}
			hdr = (alloc_hdr *) t;
			hdr->u.sz = size;
			ret = (void *) (hdr + 1);
			write_log("R %p %ld %p %ld\n", ptr, (long) old_size, ret, (long) size);
			return ret;
		}
	} else {
		if (size == 0) {
			write_log("R NULL 0 NULL 0\n");
			return NULL;
		} else {
			hdr = (alloc_hdr *) malloc(size + sizeof(alloc_hdr));
			if (!hdr) {
				write_log("R NULL 0 FAIL %ld\n", (long) size);
				return NULL;
			}
			hdr->u.sz = size;
			ret = (void *) (hdr + 1);
			write_log("R NULL 0 %p %ld\n", ret, (long) size);
			return ret;
		}
	}
}

void duk_free_logging(void *udata, void *ptr) {
	alloc_hdr *hdr;

	(void) udata;  /* Suppress warning. */

	if (!ptr) {
		write_log("F NULL 0\n");
		return;
	}
	hdr = (alloc_hdr *) (void *) ((unsigned char *) ptr - sizeof(alloc_hdr));
	write_log("F %p %ld\n", ptr, (long) hdr->u.sz);
	free((void *) hdr);
}
