// Inferno utils/6l/span.c
// http://code.google.com/p/inferno-os/source/browse/utils/6l/span.c
//
//	Copyright © 1994-1999 Lucent Technologies Inc.  All rights reserved.
//	Portions Copyright © 1995-1997 C H Forsyth (forsyth@terzarima.net)
//	Portions Copyright © 1997-1999 Vita Nuova Limited
//	Portions Copyright © 2000-2007 Vita Nuova Holdings Limited (www.vitanuova.com)
//	Portions Copyright © 2004,2006 Bruce Ellis
//	Portions Copyright © 2005-2007 C H Forsyth (forsyth@terzarima.net)
//	Revisions Copyright © 2000-2007 Lucent Technologies Inc. and others
//	Portions Copyright © 2009 The Go Authors.  All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

// Symbol table.

#include	"l.h"
#include	"../ld/lib.h"
#include	"../ld/elf.h"

static int maxelfstr;

static int
putelfstr(char *s)
{
	int off, n;
	char *p, *q;

	if(elfstrsize == 0 && s[0] != 0) {
		// first entry must be empty string
		putelfstr("");
	}

	n = strlen(s)+1;
	if(elfstrsize+n > maxelfstr) {
		maxelfstr = 2*(elfstrsize+n+(1<<20));
		elfstrdat = realloc(elfstrdat, maxelfstr);
	}
	off = elfstrsize;
	elfstrsize += n;
	memmove(elfstrdat+off, s, n);
	// replace "·" as ".", because DTrace cannot handle it.
	p = strstr(s, "·");
	if(p != nil) {
		p = q = elfstrdat+off;
		while (*q != '\0') {
			if((uchar)*q == 0xc2 && (uchar)*(q+1) == 0xb7) {
				q += 2;
				*p++ = '.';
				elfstrsize--;
			} else {
				*p++ = *q++;
			}
		}
		*p = '\0';
	}
	return off;
}

static void
putelfsyment(int off, vlong addr, vlong size, int info, int shndx, int other)
{
	switch(thechar) {
	case '6':
		LPUT(off);
		cput(info);
		cput(other);
		WPUT(shndx);
		VPUT(addr);
		VPUT(size);
		symsize += ELF64SYMSIZE;
		break;
	default:
		LPUT(off);
		LPUT(addr);
		LPUT(size);
		cput(info);
		cput(other);
		WPUT(shndx);
		symsize += ELF32SYMSIZE;
		break;
	}
}

static int numelfsym = 1; // 0 is reserved
static int elfbind;

static void
putelfsym(LSym *x, char *s, int t, vlong addr, vlong size, int ver, LSym *go)
{
	int bind, type, off;
	LSym *xo;

	USED(go);
	switch(t) {
	default:
		return;
	case 'T':
		type = STT_FUNC;
		break;
	case 'D':
		type = STT_OBJECT;
		break;
	case 'B':
		type = STT_OBJECT;
		break;
	}
	xo = x;
	while(xo->outer != nil)
		xo = xo->outer;
	if(xo->sect == nil) {
		ctxt->cursym = x;
		diag("missing section in putelfsym");
		return;
	}
	if(xo->sect->elfsect == nil) {
		ctxt->cursym = x;
		diag("missing ELF section in putelfsym");
		return;
	}

	// One pass for each binding: STB_LOCAL, STB_GLOBAL,
	// maybe one day STB_WEAK.
	bind = STB_GLOBAL;
	if(ver || (x->type & SHIDDEN))
		bind = STB_LOCAL;

	// In external linking mode, we have to invoke gcc with -rdynamic
	// to get the exported symbols put into the dynamic symbol table.
	// To avoid filling the dynamic table with lots of unnecessary symbols,
	// mark all Go symbols local (not global) in the final executable.
	if(linkmode == LinkExternal && !(x->cgoexport&CgoExportStatic))
		bind = STB_LOCAL;

	if(bind != elfbind)
		return;

	off = putelfstr(s);
	if(linkmode == LinkExternal)
		addr -= xo->sect->vaddr;
	putelfsyment(off, addr, size, (bind<<4)|(type&0xf), xo->sect->elfsect->shnum, (x->type & SHIDDEN) ? 2 : 0);
	x->elfsym = numelfsym++;
}

void
putelfsectionsym(LSym* s, int shndx)
{
	putelfsyment(0, 0, 0, (STB_LOCAL<<4)|STT_SECTION, shndx, 0);
	s->elfsym = numelfsym++;
}

void
putelfsymshndx(vlong sympos, int shndx)
{
	vlong here;

	here = cpos();
	switch(thechar) {
	case '6':
		cseek(sympos+6);
		break;
	default:
		cseek(sympos+14);
		break;
	}
	WPUT(shndx);
	cseek(here);
}

void
asmelfsym(void)
{
	LSym *s;
	char *name;

	// the first symbol entry is reserved
	putelfsyment(0, 0, 0, (STB_LOCAL<<4)|STT_NOTYPE, 0, 0);

	dwarfaddelfsectionsyms();

	elfbind = STB_LOCAL;
	genasmsym(putelfsym);
	
	if(linkmode == LinkExternal && HEADTYPE != Hopenbsd) {
		s = linklookup(ctxt, "runtime.tlsgm", 0);
		if(s->sect == nil) {
			ctxt->cursym = nil;
			diag("missing section for %s", s->name);
			errorexit();
		}
		putelfsyment(putelfstr(s->name), 0, 2*PtrSize, (STB_LOCAL<<4)|STT_TLS, s->sect->elfsect->shnum, 0);
		s->elfsym = numelfsym++;
	}

	elfbind = STB_GLOBAL;
	elfglobalsymndx = numelfsym;
	genasmsym(putelfsym);
	
	for(s=ctxt->allsym; s!=S; s=s->allsym) {
		if(s->type != SHOSTOBJ && !(s->type == SDYNIMPORT && s->reachable))
			continue;
		if(s->type == SDYNIMPORT)
			name = s->extname;
		else
			name = s->name;
		putelfsyment(putelfstr(name), 0, 0, (STB_GLOBAL<<4)|STT_NOTYPE, 0, 0);
		s->elfsym = numelfsym++;
	}
}

static void
putplan9sym(LSym *x, char *s, int t, vlong addr, vlong size, int ver, LSym *go)
{
	int i, l;

	USED(go);
	USED(ver);
	USED(size);
	USED(x);
	switch(t) {
	case 'T':
	case 'L':
	case 'D':
	case 'B':
		if(ver)
			t += 'a' - 'A';
	case 'a':
	case 'p':
	case 'f':
	case 'z':
	case 'Z':
	case 'm':
		l = 4;
		if(HEADTYPE == Hplan9 && thechar == '6' && !debug['8']) {
			lputb(addr>>32);
			l = 8;
		}
		lputb(addr);
		cput(t+0x80); /* 0x80 is variable length */

		if(t == 'z' || t == 'Z') {
			cput(s[0]);
			for(i=1; s[i] != 0 || s[i+1] != 0; i += 2) {
				cput(s[i]);
				cput(s[i+1]);
			}
			cput(0);
			cput(0);
			i++;
		} else {
			/* skip the '<' in filenames */
			if(t == 'f')
				s++;
			for(i=0; s[i]; i++)
				cput(s[i]);
			cput(0);
		}
		symsize += l + 1 + i + 1;
		break;
	default:
		return;
	};
}

void
asmplan9sym(void)
{
	genasmsym(putplan9sym);
}

static LSym *symt;

void
wputl(ushort w)
{
	cput(w);
	cput(w>>8);
}

void
wputb(ushort w)
{
	cput(w>>8);
	cput(w);
}

void
lputb(int32 l)
{
	cput(l>>24);
	cput(l>>16);
	cput(l>>8);
	cput(l);
}

void
lputl(int32 l)
{
	cput(l);
	cput(l>>8);
	cput(l>>16);
	cput(l>>24);
}

void
vputb(uint64 v)
{
	lputb(v>>32);
	lputb(v);
}

void
vputl(uint64 v)
{
	lputl(v);
	lputl(v >> 32);
}

void
symtab(void)
{
	LSym *s, *symtype, *symtypelink, *symgostring, *symgofunc;

	dosymtype();

	// Define these so that they'll get put into the symbol table.
	// data.c:/^address will provide the actual values.
	xdefine("text", STEXT, 0);
	xdefine("etext", STEXT, 0);
	xdefine("typelink", SRODATA, 0);
	xdefine("etypelink", SRODATA, 0);
	xdefine("rodata", SRODATA, 0);
	xdefine("erodata", SRODATA, 0);
	xdefine("noptrdata", SNOPTRDATA, 0);
	xdefine("enoptrdata", SNOPTRDATA, 0);
	xdefine("data", SDATA, 0);
	xdefine("edata", SDATA, 0);
	xdefine("bss", SBSS, 0);
	xdefine("ebss", SBSS, 0);
	xdefine("noptrbss", SNOPTRBSS, 0);
	xdefine("enoptrbss", SNOPTRBSS, 0);
	xdefine("end", SBSS, 0);
	xdefine("epclntab", SRODATA, 0);
	xdefine("esymtab", SRODATA, 0);

	// garbage collection symbols
	s = linklookup(ctxt, "gcdata", 0);
	s->type = SRODATA;
	s->size = 0;
	s->reachable = 1;
	xdefine("egcdata", SRODATA, 0);

	s = linklookup(ctxt, "gcbss", 0);
	s->type = SRODATA;
	s->size = 0;
	s->reachable = 1;
	xdefine("egcbss", SRODATA, 0);

	// pseudo-symbols to mark locations of type, string, and go string data.
	s = linklookup(ctxt, "type.*", 0);
	s->type = STYPE;
	s->size = 0;
	s->reachable = 1;
	symtype = s;

	s = linklookup(ctxt, "go.string.*", 0);
	s->type = SGOSTRING;
	s->size = 0;
	s->reachable = 1;
	symgostring = s;
	
	s = linklookup(ctxt, "go.func.*", 0);
	s->type = SGOFUNC;
	s->size = 0;
	s->reachable = 1;
	symgofunc = s;
	
	symtypelink = linklookup(ctxt, "typelink", 0);

	symt = linklookup(ctxt, "symtab", 0);
	symt->type = SSYMTAB;
	symt->size = 0;
	symt->reachable = 1;

	// assign specific types so that they sort together.
	// within a type they sort by size, so the .* symbols
	// just defined above will be first.
	// hide the specific symbols.
	for(s = ctxt->allsym; s != S; s = s->allsym) {
		if(!s->reachable || s->special || s->type != SRODATA)
			continue;
		if(strncmp(s->name, "type.", 5) == 0) {
			s->type = STYPE;
			s->hide = 1;
			s->outer = symtype;
		}
		if(strncmp(s->name, "go.typelink.", 12) == 0) {
			s->type = STYPELINK;
			s->hide = 1;
			s->outer = symtypelink;
		}
		if(strncmp(s->name, "go.string.", 10) == 0) {
			s->type = SGOSTRING;
			s->hide = 1;
			s->outer = symgostring;
		}
		if(strncmp(s->name, "go.func.", 8) == 0) {
			s->type = SGOFUNC;
			s->hide = 1;
			s->outer = symgofunc;
		}
		if(strncmp(s->name, "gcargs.", 7) == 0 || strncmp(s->name, "gclocals.", 9) == 0 || strncmp(s->name, "gclocals·", 10) == 0) {
			s->type = SGOFUNC;
			s->hide = 1;
			s->outer = symgofunc;
			s->align = 4;
			liveness += (s->size+s->align-1)&~(s->align-1);
		}
	}
}
