#include <u.h>
#include <libc.h>
#include <mach.h>
#include "stabs.h"

static int
strcmpcolon(char *a, char *bcolon)
{
	int i, len;
	char *p;

	p = strchr(bcolon, ':');
	if(p == nil)
		return strcmp(a, bcolon);
	len = p-bcolon;
	i = strncmp(a, bcolon, len);
	if(i)
		return i;
	if(a[len] == 0)
		return 0;
	return 1;
}

static int
stabcvtsym(StabSym *stab, Symbol *sym, char *dir, char *file, int i)
{
	char *p;

	/*
	 * Zero out the : to avoid allocating a new name string.
	 * The type info can be found by looking past the NUL.
	 * This is going to get us in trouble...
	 */
	if((p = strchr(stab->name, ':')) != nil)
		*p++ = 0;
	else
		p = stab->name+strlen(stab->name)+1;

	sym->name = stab->name;
	sym->u.stabs.dir = dir;
	sym->u.stabs.file = file;
	sym->u.stabs.i = i;
	switch(stab->type){
	default:
		return -1;
	case N_FUN:
		sym->class = CTEXT;
		switch(*p){
		default:
			return -1;
		case 'F':	/* global function */
			sym->type = 'T';
			break;
		case 'Q':	/* static procedure */
		case 'f':	/* static function */
		case 'I':	/* nested procedure */
		case 'J':	/* nested function */
			sym->type = 't';
			break;
		}
		sym->loc.type = LADDR;
		sym->loc.addr = stab->value;
		break;
	case N_GSYM:
	case N_PSYM:
	case N_LSYM:
	case N_LCSYM:
		sym->class = CDATA;
		sym->loc.type = LADDR;
		sym->loc.addr = stab->value;
		switch(*p){
		default:
			return -1;
		case 'S':	/* file-scope static variable */
			sym->type = 'd';
			break;
		case 'G':	/* global variable */
			sym->type = 'D';
			sym->loc.type = LNONE;
			break;
		case 'r':	/* register variable */
			sym->class = CAUTO;
			sym->type = 'a';
			sym->loc.type = LREG;
			sym->loc.reg = "XXX";
			break;
		case 's':	/* local variable */
			sym->class = CAUTO;
			sym->type = 'a';
			sym->loc.type = LOFFSET;
			sym->loc.offset = stab->value;
			sym->loc.reg = "XXX";
			break;
		case 'a':	/* by reference */
		case 'D':	/* f.p. parameter */
		case 'i':	/* register parameter */
		case 'p':	/* "normal" parameter */
		case 'P':	/* register parameter */
		case 'v':	/* by reference */
		case 'X':	/* function return variable */
			sym->class = CPARAM;
			sym->type = 'p';
			if(*p == 'i'){
				sym->loc.type = LREG;
				sym->loc.reg = "XXX";
			}else{
				sym->loc.type = LOFFSET;
				sym->loc.offset = stab->value;
				sym->loc.reg = "XXX";
			}
			break;
		}
		break;
	}
	return 0;	
}

static int
stabssyminit(Fhdr *fp)
{
	int i;
	char *dir, *file;
	Stab *stabs;
	StabSym sym, lastfun;
	Symbol s, *fun;
	char **inc, **xinc;
	int ninc, minc;
	int locals, autos, params;

	stabs = &fp->stabs;
	if(stabs == nil){
		werrstr("no stabs info");
		return -1;
	}

	dir = nil;
	file = nil;
	inc = nil;
	fun = nil;
	ninc = 0;
	minc = 0;
	locals = 0;
	params = 0;
	autos = 0;
	memset(&lastfun, 0, sizeof lastfun);
	for(i=0; stabsym(stabs, i, &sym)>=0; i++){
		switch(sym.type){
		case N_SO:
			if(sym.name == nil || *sym.name == 0){
				file = nil;
				break;
			}
			if(sym.name[strlen(sym.name)-1] == '/')
				dir = sym.name;
			else
				file = sym.name;
			break;
		case N_BINCL:
			if(ninc >= minc){
				xinc = realloc(inc, (ninc+32)*sizeof(inc[0]));
				if(xinc){
					memset(xinc+ninc, 0, 32*sizeof(inc[0]));
					inc = xinc;
				}
				ninc += 32;
			}
			if(ninc < minc)
				inc[ninc] = sym.name;
			ninc++;
			break;
		case N_EINCL:
			if(ninc > 0)
				ninc--;
			break;
		case N_EXCL:
			/* condensed include - same effect as previous BINCL/EINCL pair */
			break;
		case N_GSYM:	/* global variable */
			/* only includes type, so useless for now */
			break;
		case N_FUN:
			if(sym.name == nil){
				/* marks end of function */
				if(fun){
					fun->hiloc.type = LADDR;
					fun->hiloc.addr = fun->loc.addr + sym.value;
				}
				break;
			}
			if(fun && lastfun.value==sym.value && lastfun.name==sym.name){
				fun->u.stabs.locals = i;
				break;
			}
			/* create new symbol, add it */
			lastfun = sym;
			fun = nil;
			if(stabcvtsym(&sym, &s, dir, file, i) < 0)
				continue;
			if((fun = _addsym(fp, &s)) == nil)
				goto err;
			locals = 0;
			params = 0;
			autos = 0;
			break;
		case N_PSYM:
		case N_LSYM:
		case N_LCSYM:
			if(fun){
				if(fun->u.stabs.frameptr == -1){
					/*
					 * Try to distinguish functions with a real frame pointer
				 	 * from functions with a virtual frame pointer, based on 
					 * whether the first parameter is in the right location and
					 * whether the autos have negative offsets.  
					 * 
					 * This heuristic works most of the time.  On the 386, we
					 * cannot distinguish between a v. function with no autos
					 * but a frame of size 4 and a f.p. function with no autos and
					 * no frame.   Anything else we'll get right.
					 * 
					 * Another way to go about this would be to have
					 * mach-specific functions to inspect the function
					 * prologues when we're not sure.  What we have
					 * already should be enough, though.
					 */
					if(params==0 && sym.type == N_PSYM){
						if(sym.value != 8 && sym.value >= 4){
							/* XXX 386 specific, but let's find another system before generalizing */
							fun->u.stabs.frameptr = 0;
							fun->u.stabs.framesize = sym.value - 4;
						}
					}else if(sym.type == N_LSYM){
						if(sym.value >= 0){
							fun->u.stabs.frameptr = 0;
							if(params)
								fun->u.stabs.framesize = 8 - 4;
						}else
							fun->u.stabs.frameptr = 1;
					}
				}
				if(sym.type == N_PSYM)
					params++;
				if(sym.type == N_LSYM)
					autos++;
			}
			break;

		case N_STSYM:	/* static file-scope variable */
			/* create new symbol, add it */
			if(stabcvtsym(&sym, &s, dir, file, i) < 0)
				continue;
			if(_addsym(fp, &s) == nil)
				goto err;
			break;
		}
	}
	USED(locals);
	free(inc);
	return 0;

err:
	free(inc);
	return -1;
}

static int
stabspc2file(Fhdr *fhdr, u64int pc, char *buf, uint nbuf, ulong *pline)
{
	int i;
	Symbol *s;
	StabSym ss;
	ulong line, basepc;
	Loc l;

	l.type = LADDR;
	l.addr = pc;
	if((s = ffindsym(fhdr, l, CTEXT)) == nil
	|| stabsym(&fhdr->stabs, s->u.stabs.i, &ss) < 0)
		return -1;

	line = ss.desc;
	basepc = ss.value;
	for(i=s->u.stabs.i+1; stabsym(&fhdr->stabs, i, &ss) >= 0; i++){
		if(ss.type == N_FUN && ss.name == nil)
			break;
		if(ss.type == N_SLINE){
			if(basepc+ss.value > pc)
				break;
			else
				line = ss.desc;
		}
	}
	*pline = line;
	if(s->u.stabs.dir)
		snprint(buf, nbuf, "%s%s", s->u.stabs.dir, s->u.stabs.file);
	else
		snprint(buf, nbuf, "%s", s->u.stabs.file);
	return 0;
}

static int
stabsline2pc(Fhdr *fhdr, u64int startpc, ulong line, u64int *pc)
{
	int i, trigger;
	Symbol *s;
	StabSym ss;
	ulong basepc;
	Loc l;

	l.type = LADDR;
	l.addr = startpc;
	if((s = ffindsym(fhdr, l, CTEXT)) == nil)
		return -1;

	trigger = 0;
	line = ss.desc;
	basepc = ss.value;
	for(i=s->u.stabs.i+1; stabsym(&fhdr->stabs, i, &ss) >= 0; i++){
		if(ss.type == N_FUN)
			basepc = ss.value;
		if(ss.type == N_SLINE){
			if(basepc+ss.value >= startpc)
				trigger = 1;
			if(trigger && ss.desc >= line){
				*pc = basepc+ss.value;
				return 0;
			}
		}
	}
	return -1;
}

static int
stabslenum(Fhdr *fhdr, Symbol *p, char *name, uint j, Loc l, Symbol *s)
{
	int i;
	StabSym ss;

	for(i=p->u.stabs.locals; stabsym(&fhdr->stabs, i, &ss)>=0; i++){
		if(ss.type == N_FUN && ss.name == nil)
			break;
		switch(ss.type){
		case N_PSYM:
		case N_LSYM:
		case N_LCSYM:
			if(name){
				if(strcmpcolon(name, ss.name) != 0)
					break;
			}else if(l.type){
				/* wait for now */
			}else{
				if(j-- > 0)
					break;
			}
			if(stabcvtsym(&ss, s, p->u.stabs.dir, p->u.stabs.file, i) < 0)
				return -1;
			if(s->loc.type == LOFFSET){
				if(p->u.stabs.frameptr == 0)
					s->loc.reg = mach->sp;
				else
					s->loc.reg = mach->fp;
			}
			if(l.type && loccmp(&l, &s->loc) != 0)
				break;
			return 0;
		}
	}
	return -1;
}

static Loc zl;

static int
stabslookuplsym(Fhdr *fhdr, Symbol *p, char *name, Symbol *s)
{
	return stabslenum(fhdr, p, name, 0, zl, s);
}

static int
stabsindexlsym(Fhdr *fhdr, Symbol *p, uint i, Symbol *s)
{
	return stabslenum(fhdr, p, nil, i, zl, s);
}

static int
stabsfindlsym(Fhdr *fhdr, Symbol *p, Loc l, Symbol *s)
{
	return stabslenum(fhdr, p, nil, 0, l, s);
}

int
symstabs(Fhdr *fp)
{
	if(stabssyminit(fp) < 0)
		return -1;
	fp->pc2file = stabspc2file;
	fp->line2pc = stabsline2pc;
	fp->lookuplsym = stabslookuplsym;
	fp->indexlsym = stabsindexlsym;
	fp->findlsym = stabsfindlsym;
	return 0;
}
