/* Byte code fragment construction functions */

#include "types.h"

/* Initialize a fragment */

void iz_frag(Frag *f, ptrdiff_t alignment)
{
	f->start = (unsigned char *)joe_malloc(f->size = 1024);
	f->len = 0;
	f->align = alignment;
}

void fin_code(Frag *f)
{
	f->start = (unsigned char *)joe_realloc(f->start, f->len);
	f->size = f->len;
}

void clr_frag(Frag *f)
{
	joe_free(f->start);
}

/* Expand a fragment */

/* Expand code block by at least 'size' words */

static void expand_frag(Frag *frag, ptrdiff_t size)
{
	if ((frag->size >> 1) > size)
		// Grow by 50%
		frag->size = frag->size + (frag->size >> 1);
	else
		// Grow by 50% plus requested size
		frag->size += frag->size + (frag->size >> 1);

	frag->start = (unsigned char *)joe_realloc(frag->start, frag->size);
}

/* Emit a byte: this does no alignment */

ptrdiff_t emitb_noalign(Frag *f, char c)
{
	ptrdiff_t start;
	if (f->len + SIZEOF(unsigned char) > f->size)
		expand_frag(f, SIZEOF(unsigned char));
	start = f->len;
	f->start[f->len++] = (unsigned char)c;
	return start;
}

/* Align to some power of 2 */

void align_frag(Frag *f,ptrdiff_t alignment)
{
	ptrdiff_t x;
	ptrdiff_t add = align_o(f->len, alignment);
	for (x = 0; x != add; ++x)
		emitb_noalign(f, '-');
}

/* Emit a byte and align */

ptrdiff_t emitb(Frag *f, char c)
{
	ptrdiff_t ofst = emitb_noalign(f, c);
	if (f->len & (f->align - 1))
		align_frag(f, f->align);
	return ofst;
}

/* Emit a short */

ptrdiff_t emith(Frag *f, short c)
{
	ptrdiff_t start;
	if (f->len & (SIZEOF(short) - 1))
		align_frag(f, SIZEOF(short));
	if (f->len + SIZEOF(short) > f->size)
		expand_frag(f, SIZEOF(short));
	start = f->len;
	*(short *)(f->start + f->len) = c;
	f->len += SIZEOF(short);
	if (f->len & (f->align - 1))
		align_frag(f, f->align);
	return start;
}

/* Emit an integer */

ptrdiff_t emiti(Frag *f, int c)
{
	ptrdiff_t start;
	if (f->len & (SIZEOF(int) - 1))
		align_frag(f, SIZEOF(int));
	if (f->len + SIZEOF(int) > f->size)
		expand_frag(f, SIZEOF(int));
	start = f->len;
	*(int *)(f->start + f->len) = c;
	f->len += SIZEOF(int);
	if (f->len & (f->align - 1))
		align_frag(f, f->align);
	return start;
}

/* Emit a double */

ptrdiff_t emitd(Frag *f, double d)
{
	ptrdiff_t start;
	if (f->len & (SIZEOF(double) - 1))
		align_frag(f, SIZEOF(double));
	if (f->len + SIZEOF(double) > f->size)
		expand_frag(f, SIZEOF(double));
	start = f->len;
	*(double *)(f->start + f->len) = d;
	f->len += SIZEOF(double);
	if (f->len & (f->align - 1))
		align_frag(f, f->align);
	return start;
}

/* Emit a pointer */

ptrdiff_t emitp(Frag *f, void *p)
{
	ptrdiff_t start;
	if (f->len & (SIZEOF(void *) - 1))
		align_frag(f, SIZEOF(void *));
	if (f->len + SIZEOF(void *) > f->size)
		expand_frag(f, SIZEOF(void *));
	start = f->len;
	*(void **)(f->start + f->len) = p;
	f->len += SIZEOF(void *);
	if (f->len & (f->align - 1))
		align_frag(f, f->align);
	return start;
}

/* Append a string to the code block */

ptrdiff_t emits(Frag *f, unsigned char *s, int len)
{
	ptrdiff_t start;

	start = emiti(f, len);

	if (f->len + len + 1 > f->size)
		expand_frag(f, len + 1);

	if (len)
		mcpy(f->start + f->len, s, len);

	f->start[f->len + len] = 0;

	f->len += len + 1;

	if (f->len & (f->align - 1))
		align_frag(f, f->align);

	return start;
}

ptrdiff_t emit_branch(Frag *f, ptrdiff_t target)
{
	ptrdiff_t ofst = emiti(f, 0);
	fragi(f, ofst) = (int)(target - ofst);
	return ofst;
}

void fixup_branch(Frag *f, ptrdiff_t pos)
{
	align_frag(f, SIZEOF(int));
	fragi(f, pos) = (int)(f->len - pos);
}

/* Modify each item in a linked-list to point to here */

void frag_link(Frag *f, ptrdiff_t chain)
{
	ptrdiff_t ket;
	align_frag(f, SIZEOF(int));
	ket = f->len;
	while (chain) {
		int next = fragi(f, chain);
		fragi(f, chain) = (int)(ket - chain);
		chain = next;
	}
}

short fetchh(Frag *f, ptrdiff_t *pcp)
{
	ptrdiff_t pc = *pcp;
	short i;
	pc += align_o(pc, SIZEOF(short));
	i = fragh(f, pc);
	pc += SIZEOF(short);
	if (pc & (f->align - 1))
		pc += align_o(pc, f->align);
	*pcp = pc;
	return i;
}

int fetchi(Frag *f, ptrdiff_t *pcp)
{
	ptrdiff_t pc = *pcp;
	int i;
	pc += align_o(pc, SIZEOF(int));
	i = fragi(f, pc);
	pc += SIZEOF(int);
	if (pc & (f->align - 1))
		pc += align_o(pc, f->align);
	*pcp = pc;
	return i;
}

void *fetchp(Frag *f, ptrdiff_t *pcp)
{
	ptrdiff_t pc = *pcp;
	void *p;
	pc += align_o(pc, SIZEOF(void *));
	p = fragp(f, pc);
	pc += SIZEOF(void *);
	if (pc & (f->align - 1))
		pc += align_o(pc, f->align);
	*pcp = pc;
	return p;
}
