#include	"l.h"

#define KIF	"kernel interface file"

static Prog *datp;
static Sym *linkt;

static int *strind;

static Prog* newprg(int, Prog*);

static int
isstring(Sym *s)
{
	return s != S && strcmp(s->name, ".string") == 0;
}

void
kif(char *f)
{	
	int i, n, d;
	long o;
	Prog *p, *q, *fp, *lp, *dp;
	Sym *s;

	s = lookup("__link", 0);
	if(s->type != 0){
		diag("dynamic link table entry __link already defined\n");
		errorexit();
	}
	s->type = SBSS;
	s->value = sizeof(long*);
	linkt = s;

	fp = firstp;
	lp = lastp;
	dp = datap;
	objfile(f);
	if(fp != firstp || lp != lastp){
		diag("code in %s %s\n", KIF, f);
		errorexit();
	}
	p = datap;
	datap = dp;
	dp = P;
	for( ; p != P && p != datap; p = q){
		q = p->link;
		p->link = dp;
		dp = p;
	}

	o = 0;
	s = S;
	lp = nil;
	d = 0;
	for(p = dp; p != P; p = p->link){
		if(isstring(p->from.sym) || isstring(p->to.sym)){
			if(lp == nil)
				dp = p->link;
			else
				lp->link = p->link;
			continue;
		}
		lp = p;
		if(p->as == ADYNT || p->as == AINIT){
			diag("bad data type in %s %s\n", KIF, f);
			errorexit();
		}
		if(s == S)
			s = p->from.sym;
		if(s != p->from.sym){
			diag("more than one data item in %s %s\n", KIF, f);
			errorexit();
		}
		if(d == 0 && p->from.offset > 0){
			d = p->from.offset;
			o += d;
		}
		if(o != p->from.offset){
			diag("bad data size in %s %s\n", KIF, f);
			errorexit();
		}
		o += d;
		if(p->to.sym == nil){
			diag("bad pointer value in %s %s\n", KIF, f);
			errorexit();
		}
		if(p->to.sym->type != SXREF){
			diag("%s pointer value %s already defined\n", KIF, p->to.sym->name);
			errorexit();
		}
	}
	datp = dp;
	n = o/d;
	strind = (int *)malloc(n*sizeof(int));
	for(i = 0; i < n; i++)
		strind[i] = 0;
}

static Prog*
newprg(int op, Prog* p)
{
	Prog* q;

	q = prg();
	q->as = op;
	q->line = p->line;
	q->pc = p->pc;
	q->reg = NREG;
	q->link = p->link;
	p->link = q;
	return q;
}

int
dynfn(Sym *s)
{
	int i, k;
	Prog *dp;

	k = -1;
	for(i = 0, dp = datp; dp != P; i++, dp = dp->link){
		if(s == dp->to.sym){
			k = i;
			strind[k] = 1;	/* used */
			break;
		}
	}
	return k;
}

int
dyncall(Prog *p, int div)
{
	int k;
	Prog *q, *q0, t;

	if((k = dynfn(p->to.sym)) < 0)
		return 0;

	if(!seenthumb && debug['y']){
		p->to.offset = 0;
		p->to.type = D_BRANCH;
		p->cond = P;
		return 1;
	}

#define R	1

	if(!seenthumb && debug['x']){
		p->to.sym->value = 0x7fffff00;
		p->to.sym->type = SDYN;

		p->as = AMOVW;
		p->from.type = D_CONST;
		p->from.name = D_EXTERN;
		p->from.sym = p->to.sym;
		p->from.offset = k;
		p->reg = NREG;
		p->to.type = D_REG;
		p->to.name = D_NONE;
		p->to.sym = S;
		p->to.reg = R;

		q = newprg(ABL, p);
		q->to.type = D_OREG;
		q->to.offset = 0;
		q->to.reg = R;
		q->scond = p->scond;
	}
	else{
		p->as = AMOVW;
		p->from.type = D_OREG;
		p->from.name = D_EXTERN;
		p->from.sym = linkt;
		p->from.offset = 0;
		p->reg = NREG;
		p->to.type = D_REG;
		p->to.name = D_NONE;
		p->to.sym = nil;
		p->to.reg = R;
	
		q = newprg(AMOVW, p);
		q->from.type = D_OREG;
		q->from.name = D_NONE;
		q->from.reg = R;
		q->from.offset = 3*k*sizeof(long*);
		q->to.type = D_REG;
		q->to.reg = R;
		q->scond = p->scond;

		q = newprg(ABL, q);
		q->from.type = D_NONE;
		q->to.type = D_OREG;
		q->to.name = D_NONE;
		q->to.reg = R;
		q->to.offset = 0;
		q->scond = p->scond;
	}

	if(div){
		q0 = newprg(AMOVW, p);
		q0->from.type = D_REG;
		q0->from.reg = R;
		q0->to.type = D_OREG;
		q0->to.reg = REGSP;
		q0->to.offset = 0;
		q0->scond = p->scond;

		t = *p;
		*p = *q0;
		*q0 = t;
		q0->link = p->link;
		p->link = q0;

		q = newprg(AMOVW, q);
		q->from.type = D_OREG;
		q->from.reg = REGSP;
		q->from.offset = 0;
		q->to.type = D_REG;
		q->to.reg = R;
		q->scond = p->scond;
	}

#undef R

	return 1;
}

static struct relocinfo{
	long n;
	long m;
	long *rel;
} reloc[5];

static void
grow(struct relocinfo *r)
{
	int n;
	long *nrel;

	n = r->m;
	r->m += 32;
	nrel = malloc(r->m*sizeof(long));
	memmove(nrel, r->rel, n*sizeof(long));
	free(r->rel);
	r->rel = nrel;
}

static int
inrel(long a, struct relocinfo *r)
{
	int i;
	long n, *rel;

	n = r->n;
	rel = r->rel;
	for(i = 0; i < n; i++)
		if(rel[i] == a)
			return 1;
	return 0;
}

void
dynreloc(long a, int intext, Sym *s)
{
	int i = 0;
	struct relocinfo *r;

	if(!intext)
		i = 2;
	if(s == S || s->type == STEXT || s->type == SLEAF)
		;
	else if(s->type == SDATA || s->type == SDATA1 || s->type == SBSS)
		i++;
	else if(s->type == SXREF)
		return;
	else
		diag("help: dynreloc cannot cope\n");
	r = &reloc[i];
	if(inrel(a, r))
		return;
	if(r->n >= r->m)
		grow(r);
	r->rel[r->n++] = a;
}

void
creloc(int f, long a)
{
	struct relocinfo *r = &reloc[4];

	if(f < 0 || f > 255){
		diag("bad function table index");
		errorexit();
	}
	if(inrel(a, r))
		return;
	if(r->n >= r->m)
		grow(r);
	r->rel[r->n++] = f;
	r->rel[r->n++] = a;
}

static void
outrel(struct relocinfo *r)
{
	int i, n = r->n;
	long *p;

	lput(n);
	for(i = 0, p = r->rel; i < n; i++, p++)
		lput(*p);
}

static void
outstrtab(int sz)
{
	int k;
	char *s;
	Prog *dp;

	lput(sz);
	for(k = 0, dp = datp; dp != P; k++, dp = dp->link){
		if(strind[k] >= 0){
			for(s = dp->to.sym->name; *s != '\0'; s++)
				cput(*s);
			cput('\0');
		}
	}
}

static void
dostrind(void)
{
	int i, k;
	Prog *dp;

	i = 0;
	for(k = 0, dp = datp; dp != P; k++, dp = dp->link){
		if(strind[k]){
			strind[k] = i;
			i += strlen(dp->to.sym->name)+1;
		}
		else
			strind[k] = -1;
	}
	outstrtab(i);
}

static void
coutrel(void)
{
	struct relocinfo *r = &reloc[4];
	int i, n = r->n;
	long *p;

	dostrind();
	if(debug['y'])
		cput(0x80+24);	/* 24 bit relative */
	else if(debug['x'])
		cput(32);			/* 32 bit absolute */
	else
		diag("bad coutrel");
	lput(n/2);
	for(i = 0, p = r->rel; i < n; i++){
		/* cput(*p++); */
		hput(strind[*p++]);
		lput(*p++);
	}
}

static void
dreloc(void)
{
	Prog *p;
	long a;

	for(p = datap; p != P; p = p->link){
		if(p->to.type == D_CONST && p->to.sym){
			if(isfnptr(&p->to) && dynfn(p->to.sym) >= 0)
				diag("cannot use interface function %s as a function pointer", p->to.sym->name);
			a = p->from.sym->value + p->from.offset + INITDAT;
			dynreloc(a, 0, p->to.sym);
		}
		/* print("%P\n", p); */
	}	
}

void
asmdyn(void)
{
	int i;
	Prog *p;
	Sym *s;
	char buf[32];

	dreloc();

	for(p = datp; p != P; p = p->link)
		p->to.sym->type = STEXT;	// stop undefined messages at end

	lput(0xfedcba98);
	if(module != nil){
		for(i = 0; module[i]; i++)
			cput(module[i]);
	}
	cput(0);
	if(module != nil){
		strcpy(buf, module);
		strcat(buf, "end");
		s = lookup(buf, 0);
		if(s->type == STEXT){
			if(s->thumb)
				lput(s->value+1);	// T bit
			else
				lput(s->value);
		}
		else
			lput(-1);
	}
	else
		lput(-1);
	if(linkt != S)
		lput(linkt->value);
	else
		lput(-1);
	if(module != nil){
		strcpy(buf, module);
		strcat(buf, "modtab");
		s = lookup(buf, 0);
		if(s == nil){
			diag("missing module table: %s\n", buf);
			errorexit();
		}
		lput(s->value);
	}
	else
		lput(-1);
	// [tt|td|dt|dd]reloc
	for(i = 0; i < 4; i++)
		outrel(&reloc[i]);
	// call reloc
	if(reloc[4].n > 0){
		lput(0x89abcdef);
		coutrel();
	}
}

long
dynentry(Sym *s)
{
	char buf[32];

	s->type = STEXT;	// prevent error message
	if(module == nil)
		return -1;
	strcpy(buf, module);
	strcat(buf, "init");
/*
	for(p = buf; *p != 0; p++){
		if(*p >= 'A' && *p <= 'Z')
			*p += 'a' - 'A';
	}
*/
	s = lookup(buf, 0);
	if(s->type == STEXT){
		if(s->thumb)
			return  s->value+1;	// T bit
		else
			return s->value;
	}
	return -1;
}