#include "limbo.h"

static	void	disbig(long, Long);
static	void	disbyte(long, int);
static	void	disbytes(long, void*, int);
static	void	disdatum(long, Node*);
static	void	disflush(int, long, long);
static	void	disint(long, long);
static	void	disreal(long, Real);
static	void	disstring(long, Sym*);

static	uchar	*cache;
static	int	ncached;
static	int	ndatum;
static	int	startoff;
static	int	lastoff;
static	int	lastkind;
static	int	lencache;

void
discon(long val)
{
	if(val >= -64 && val <= 63) {
		Bputc(bout, val & ~0x80);
		return;
	}
	if(val >= -8192 && val <= 8191) {
		Bputc(bout, ((val>>8) & ~0xC0) | 0x80);
		Bputc(bout, val);
		return;
	}
	if(val < 0 && ((val >> 29) & 0x7) != 7
	|| val > 0 && (val >> 29) != 0)
		fatal("overflow in constant 0x%lux\n", val);
	Bputc(bout, (val>>24) | 0xC0);
	Bputc(bout, val>>16);
	Bputc(bout, val>>8);
	Bputc(bout, val);
}

void
disword(long w)
{
	Bputc(bout, w >> 24);
	Bputc(bout, w >> 16);
	Bputc(bout, w >> 8);
	Bputc(bout, w);
}

void
disdata(int kind, long n)
{
	if(n < DMAX && n != 0)
		Bputc(bout, DBYTE(kind, n));
	else{
		Bputc(bout, DBYTE(kind, 0));
		discon(n);
	}
}

#define	NAMELEN	64

void
dismod(Decl *m)
{
	char name[8*NAMELEN];
	ulong fileoff;

	fileoff = Boffset(bout);
	strncpy(name, m->sym->name, NAMELEN);
	name[NAMELEN-1] = '\0';
	Bwrite(bout, name, strlen(name)+1);
	for(m = m->ty->tof->ids; m != nil; m = m->next){
		switch(m->store){
		case Dglobal:
			discon(-1);
			discon(-1);
			disword(sign(m));
			Bprint(bout, ".mp");
			Bputc(bout, '\0');
			break;
		case Dfn:
if(debug['v']) print("Dfn: %s %d %p\n", m->sym->name, m->refs, m);
			discon(m->pc->pc);
			discon(m->desc->id);
			disword(sign(m));
			if(m->dot->ty->kind == Tadt)
				Bprint(bout, "%s.", m->dot->sym->name);
			Bprint(bout, "%s", m->sym->name);
			Bputc(bout, '\0');
			break;
		default:
			fatal("unknown kind %K in dismod", m);
			break;
		}
	}
	if(debug['s'])
		print("%ld linkage bytes start %ld\n", Boffset(bout) - fileoff, fileoff);
}

void
dispath(void)
{
	char name[8*NAMELEN], *sp;

	sp = srcpath(name, 8*NAMELEN);
	Bwrite(bout, sp, strlen(sp)+1);
}

void
disentry(Decl *e)
{
	if(e == nil){
		discon(-1);
		discon(-1);
		return;
	}
	discon(e->pc->pc);
	discon(e->desc->id);
}

void
disdesc(Desc *d)
{
	ulong fileoff;

	fileoff = Boffset(bout);
	for(; d != nil; d = d->next){
		discon(d->id);
		discon(d->size);
		discon(d->nmap);
		Bwrite(bout, d->map, d->nmap);
	}
	if(debug['s'])
		print("%ld type descriptor bytes start %ld\n", Boffset(bout) - fileoff, fileoff);
}

void
disvar(long size, Decl *d)
{
	ulong fileoff;

	fileoff = Boffset(bout);
	USED(size);

	lastkind = -1;
	ncached = 0;
	ndatum = 0;

	for(; d != nil; d = d->next)
		if(d->store == Dglobal && d->init != nil)
			disdatum(d->offset, d->init);

	disflush(-1, -1, 0);

	Bputc(bout, 0);

	if(debug['s'])
		print("%ld data bytes start %ld\n", Boffset(bout) - fileoff, fileoff);
}

void
disldt(long size, Decl *ds)
{
	int m;
	Decl *d, *id;
	Sym *s;
	Node *n;

	if(0){
		discon(size);
		disvar(size, ds);
		return;
	}

	m = 0;
	for(d = ds; d != nil; d = d->next)
		if(d->store == Dglobal && d->init != nil)
			m++;
	discon(m);
	for(d = ds; d != nil; d = d->next){
		if(d->store == Dglobal && d->init != nil){
			n = d->init;
			if(n->ty->kind != Tiface)
				nerror(n, "disldt: not Tiface");
			discon(n->val);
			for(id = n->decl->ty->ids; id != nil; id = id->next){
				disword(sign(id));
				if(id->dot->ty->kind == Tadt){
					s = id->dot->sym;
					Bprint(bout, "%s", s->name);
					Bputc(bout, '.');
				}
				s = id->sym;
				Bprint(bout, "%s", s->name);
				Bputc(bout, 0);
			}
		}
	}
	discon(0);
}

static void
disdatum(long offset, Node *n)
{
	Node *elem, *wild;
	Case *c;
	Label *lab;
	Decl *id;
	Sym *s;
	long e, last, esz;
	int i;

	switch(n->ty->kind){
	case Tbyte:
		disbyte(offset, n->val);
		break;
	case Tint:
	case Tfix:
		disint(offset, n->val);
		break;
	case Tbig:
		disbig(offset, n->val);
		break;
	case Tstring:
		disstring(offset, n->decl->sym);
		break;
	case Treal:
		disreal(offset, n->rval);
		break;
	case Tadt:
	case Tadtpick:
	case Ttuple:
		id = n->ty->ids;
		for(n = n->left; n != nil; n = n->right){
			disdatum(offset + id->offset, n->left);
			id = id->next;
		}
		break;
	case Tany:
		break;
	case Tcase:
		c = n->ty->cse;
		disint(offset, c->nlab);
		offset += IBY2WD;
		for(i = 0; i < c->nlab; i++){
			lab = &c->labs[i];
			disint(offset, lab->start->val);
			offset += IBY2WD;
			disint(offset, lab->stop->val+1);
			offset += IBY2WD;
			disint(offset, lab->inst->pc);
			offset += IBY2WD;
		}
		disint(offset, c->iwild ? c->iwild->pc : -1);
		break;
	case Tcasel:
		c = n->ty->cse;
		disint(offset, c->nlab);
		offset += 2*IBY2WD;
		for(i = 0; i < c->nlab; i++){
			lab = &c->labs[i];
			disbig(offset, lab->start->val);
			offset += IBY2LG;
			disbig(offset, lab->stop->val+1);
			offset += IBY2LG;
			disint(offset, lab->inst->pc);
			offset += 2*IBY2WD;
		}
		disint(offset, c->iwild ? c->iwild->pc : -1);
		break;
	case Tcasec:
		c = n->ty->cse;
		disint(offset, c->nlab);
		offset += IBY2WD;
		for(i = 0; i < c->nlab; i++){
			lab = &c->labs[i];
			disstring(offset, lab->start->decl->sym);
			offset += IBY2WD;
			if(lab->stop != lab->start)
				disstring(offset, lab->stop->decl->sym);
			offset += IBY2WD;
			disint(offset, lab->inst->pc);
			offset += IBY2WD;
		}
		disint(offset, c->iwild ? c->iwild->pc : -1);
		break;
	case Tgoto:
		c = n->ty->cse;
		disint(offset, n->ty->size/IBY2WD-1);
		offset += IBY2WD;
		for(i = 0; i < c->nlab; i++){
			disint(offset, c->labs[i].inst->pc);
			offset += IBY2WD;
		}
		if(c->iwild != nil)
			disint(offset, c->iwild->pc);
		break;
	case Tarray:
		disflush(-1, -1, 0);
		disdata(DEFA, 1);		/* 1 is ignored */
		discon(offset);
		disword(n->ty->tof->decl->desc->id);
		disword(n->left->val);

		if(n->right == nil)
			break;

		disdata(DIND, 1);		/* 1 is ignored */
		discon(offset);
		disword(0);

		c = n->right->ty->cse;
		wild = nil;
		if(c->wild != nil)
			wild = c->wild->right;
		last = 0;
		esz = n->ty->tof->size;
		for(i = 0; i < c->nlab; i++){
			e = c->labs[i].start->val;
			if(wild != nil){
				for(; last < e; last++)
					disdatum(esz * last, wild);
			}
			last = e;
			e = c->labs[i].stop->val;
			elem = c->labs[i].node->right;
			for(; last <= e; last++)
				disdatum(esz * last, elem);
		}
		if(wild != nil)
			for(e = n->left->val; last < e; last++)
				disdatum(esz * last, wild);

		disflush(-1, -1, 0);
		disdata(DAPOP, 1);		/* 1 is ignored */
		discon(0);

		break;
	case Tiface:
		disint(offset, n->val);
		offset += IBY2WD;
		for(id = n->decl->ty->ids; id != nil; id = id->next){
			offset = align(offset, IBY2WD);
			disint(offset, sign(id));
			offset += IBY2WD;

			if(id->dot->ty->kind == Tadt){
				s = id->dot->sym;
				disbytes(offset, s->name, s->len);
				offset += s->len;
				disbyte(offset, '.');
				offset++;
			}
			s = id->sym;
			disbytes(offset, s->name, s->len);
			offset += s->len;
			disbyte(offset, 0);
			offset++;
		}
		break;
	default:
		nerror(n, "can't dis global %n", n);
		break;
	}
}

void
disexc(Except *es)
{
	int i, n;
	Decl *d;
	Except *e;
	Case *c;
	Label *lab;

	n = 0;
	for(e = es; e != nil; e = e->next)
		if(e->p1->reach || e->p2->reach)
			n++;
	discon(n);
	for(e = es; e != nil; e = e->next){
		if(!e->p1->reach && !e->p2->reach)
			continue;
		c = e->c;
		discon(e->d->offset);
		discon(getpc(e->p1));
		discon(getpc(e->p2));
		if(e->desc)
			discon(e->desc->id);
		else
			discon(-1);
		discon(c->nlab|(e->ne<<16));
		for(i = 0; i < c->nlab; i++){
			lab = &c->labs[i];
			d = lab->start->decl;
			if(lab->start->ty->kind == Texception)
				d = d->init->decl;
			Bprint(bout, "%s", d->sym->name);
			Bputc(bout, '\0');
			discon(lab->inst->pc);
		}
		if(c->iwild == nil)
			discon(-1);
		else
			discon(c->iwild->pc);
	}
	discon(0);
}

static void
disbyte(long off, int v)
{
	disflush(DEFB, off, 1);
	cache[ncached++] = v;
	ndatum++;
}

static void
disbytes(long off, void *v, int n)
{
	disflush(DEFB, off, n);
	memmove(&cache[ncached], v, n);
	ncached += n;
	ndatum += n;
}

static void
disint(long off, long v)
{
	disflush(DEFW, off, IBY2WD);
	cache[ncached++] = v >> 24;
	cache[ncached++] = v >> 16;
	cache[ncached++] = v >> 8;
	cache[ncached++] = v;
	ndatum++;
}

static void
disbig(long off, Long v)
{
	ulong iv;

	disflush(DEFL, off, IBY2LG);
	iv = v >> 32;
	cache[ncached++] = iv >> 24;
	cache[ncached++] = iv >> 16;
	cache[ncached++] = iv >> 8;
	cache[ncached++] = iv;
	iv = v;
	cache[ncached++] = iv >> 24;
	cache[ncached++] = iv >> 16;
	cache[ncached++] = iv >> 8;
	cache[ncached++] = iv;
	ndatum++;
}

static void
disreal(long off, Real v)
{
	ulong bv[2];
	ulong iv;

	disflush(DEFF, off, IBY2LG);
	dtocanon(v, bv);
	iv = bv[0];
	cache[ncached++] = iv >> 24;
	cache[ncached++] = iv >> 16;
	cache[ncached++] = iv >> 8;
	cache[ncached++] = iv;
	iv = bv[1];
	cache[ncached++] = iv >> 24;
	cache[ncached++] = iv >> 16;
	cache[ncached++] = iv >> 8;
	cache[ncached++] = iv;
	ndatum++;
}

static void
disstring(long offset, Sym *sym)
{
	disflush(-1, -1, 0);
	disdata(DEFS, sym->len);
	discon(offset);
	Bwrite(bout, sym->name, sym->len);
}

static void
disflush(int kind, long off, long size)
{
	if(kind != lastkind || off != lastoff){
		if(lastkind != -1 && ncached){
			disdata(lastkind, ndatum);
			discon(startoff);
			Bwrite(bout, cache, ncached);
		}
		startoff = off;
		lastkind = kind;
		ncached = 0;
		ndatum = 0;
	}
	lastoff = off + size;
	while(kind >= 0 && ncached + size >= lencache){
		lencache = ncached+1024;
		cache = reallocmem(cache, lencache);
	}
}

static int dismode[Aend] = {
	/* Aimm */	AIMM,
	/* Amp */	AMP,
	/* Ampind */	AMP|AIND,
	/* Afp */	AFP,
	/* Afpind */	AFP|AIND,
	/* Apc */	AIMM,
	/* Adesc */	AIMM,
	/* Aoff */	AIMM,
	/* Anoff */	AIMM,
	/* Aerr */	AXXX,
	/* Anone */	AXXX,
	/* Aldt */	AIMM,
};

static int disregmode[Aend] = {
	/* Aimm */	AXIMM,
	/* Amp */	AXINM,
	/* Ampind */	AXNON,
	/* Afp */	AXINF,
	/* Afpind */	AXNON,
	/* Apc */	AXIMM,
	/* Adesc */	AXIMM,
	/* Aoff */	AXIMM,
	/* Anoff */	AXIMM,
	/* Aerr */	AXNON,
	/* Anone */	AXNON,
	/* Aldt */	AXIMM,
};

enum
{
	MAXCON	= 4,
	MAXADDR	= 2*MAXCON,
	MAXINST	= 3*MAXADDR+2,
	NIBUF	= 1024
};

static	uchar	*ibuf;
static	int	nibuf;

void
disinst(Inst *in)
{
	ulong fileoff;

	fileoff = Boffset(bout);
	ibuf = allocmem(NIBUF);
	nibuf = 0;
	for(; in != nil; in = in->next){
		if(in->op == INOOP)
			continue;
		if(nibuf >= NIBUF-MAXINST){
			Bwrite(bout, ibuf, nibuf);
			nibuf = 0;
		}
		ibuf[nibuf++] = in->op;
		ibuf[nibuf++] = SRC(dismode[in->sm]) | DST(dismode[in->dm]) | disregmode[in->mm];
		if(in->mm != Anone)
			disaddr(in->mm, &in->m);
		if(in->sm != Anone)
			disaddr(in->sm, &in->s);
		if(in->dm != Anone)
			disaddr(in->dm, &in->d);
	}
	if(nibuf > 0)
		Bwrite(bout, ibuf, nibuf);
	free(ibuf);
	ibuf = nil;

	if(debug['s'])
		print("%ld instruction bytes start %ld\n", Boffset(bout) - fileoff, fileoff);
}

void
disaddr(int m, Addr *a)
{
	long val;

	val = 0;
	switch(m){
	case Anone:
	case Aerr:
	default:
		break;
	case Aimm:
	case Apc:
	case Adesc:
		val = a->offset;
		break;
	case Aoff:
		val = a->decl->iface->offset;
		break;
	case Anoff:
		val = -(a->decl->iface->offset+1);
		break;
	case Afp:
	case Amp:
	case Aldt:
		val = a->reg;
		break;
	case Afpind:
	case Ampind:
		disbcon(a->reg);
		val = a->offset;
		break;
	}
	disbcon(val);
}

void
disbcon(long val)
{
	if(val >= -64 && val <= 63){
		ibuf[nibuf++] = val & ~0x80;
		return;
	}
	if(val >= -8192 && val <= 8191){
		ibuf[nibuf++] = val>>8 & ~0xC0 | 0x80;
		ibuf[nibuf++] = val;
		return;
	}
	if(val < 0 && ((val >> 29) & 7) != 7
	|| val > 0 && (val >> 29) != 0)
		fatal("overflow in constant 16r%lux", val);
	ibuf[nibuf++] = val>>24 | 0xC0;
	ibuf[nibuf++] = val>>16;
	ibuf[nibuf++] = val>>8;
	ibuf[nibuf++] = val;
}
