// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

/*
 * portable half of code generator.
 * mainly statements and control flow.
 */

#include <u.h>
#include <libc.h>
#include "go.h"

static void	cgen_dcl(Node *n);
static void	cgen_proc(Node *n, int proc);
static void	checkgoto(Node*, Node*);

static Label *labellist;
static Label *lastlabel;

Node*
sysfunc(char *name)
{
	Node *n;

	n = newname(pkglookup(name, runtimepkg));
	n->class = PFUNC;
	return n;
}

/*
 * the address of n has been taken and might be used after
 * the current function returns.  mark any local vars
 * as needing to move to the heap.
 */
void
addrescapes(Node *n)
{
	char buf[100];
	Node *oldfn;

	switch(n->op) {
	default:
		// probably a type error already.
		// dump("addrescapes", n);
		break;

	case ONAME:
		if(n == nodfp)
			break;

		// if this is a tmpname (PAUTO), it was tagged by tmpname as not escaping.
		// on PPARAM it means something different.
		if(n->class == PAUTO && n->esc == EscNever)
			break;

		if(debug['N'] && n->esc != EscUnknown)
			fatal("without escape analysis, only PAUTO's should have esc: %N", n);

		switch(n->class) {
		case PPARAMREF:
			addrescapes(n->defn);
			break;
		case PPARAM:
		case PPARAMOUT:
			// if func param, need separate temporary
			// to hold heap pointer.
			// the function type has already been checked
			// (we're in the function body)
			// so the param already has a valid xoffset.

			// expression to refer to stack copy
			n->stackparam = nod(OPARAM, n, N);
			n->stackparam->type = n->type;
			n->stackparam->addable = 1;
			if(n->xoffset == BADWIDTH)
				fatal("addrescapes before param assignment");
			n->stackparam->xoffset = n->xoffset;
			// fallthrough

		case PAUTO:
			n->class |= PHEAP;
			n->addable = 0;
			n->ullman = 2;
			n->xoffset = 0;

			// create stack variable to hold pointer to heap
			oldfn = curfn;
			curfn = n->curfn;
			n->heapaddr = temp(ptrto(n->type));
			snprint(buf, sizeof buf, "&%S", n->sym);
			n->heapaddr->sym = lookup(buf);
			n->heapaddr->orig->sym = n->heapaddr->sym;
			if(!debug['N'])
				n->esc = EscHeap;
			if(debug['m'])
				print("%L: moved to heap: %N\n", n->lineno, n);
			curfn = oldfn;
			break;
		}
		break;

	case OIND:
	case ODOTPTR:
		break;

	case ODOT:
	case OINDEX:
		// ODOTPTR has already been introduced,
		// so these are the non-pointer ODOT and OINDEX.
		// In &x[0], if x is a slice, then x does not
		// escape--the pointer inside x does, but that
		// is always a heap pointer anyway.
		if(!isslice(n->left->type))
			addrescapes(n->left);
		break;
	}
}

void
clearlabels(void)
{
	Label *l;

	for(l=labellist; l!=L; l=l->link)
		l->sym->label = L;
	
	labellist = L;
	lastlabel = L;
}

static Label*
newlab(Node *n)
{
	Sym *s;
	Label *lab;
	
	s = n->left->sym;
	if((lab = s->label) == L) {
		lab = mal(sizeof(*lab));
		if(lastlabel == nil)
			labellist = lab;
		else
			lastlabel->link = lab;
		lastlabel = lab;
		lab->sym = s;
		s->label = lab;
	}
	
	if(n->op == OLABEL) {
		if(lab->def != N)
			yyerror("label %S already defined at %L", s, lab->def->lineno);
		else
			lab->def = n;
	} else
		lab->use = list(lab->use, n);

	return lab;
}

void
checklabels(void)
{
	Label *lab;
	NodeList *l;

	for(lab=labellist; lab!=L; lab=lab->link) {
		if(lab->def == N) {
			for(l=lab->use; l; l=l->next)
				yyerrorl(l->n->lineno, "label %S not defined", lab->sym);
			continue;
		}
		if(lab->use == nil && !lab->used) {
			yyerrorl(lab->def->lineno, "label %S defined and not used", lab->sym);
			continue;
		}
		if(lab->gotopc != P)
			fatal("label %S never resolved", lab->sym);
		for(l=lab->use; l; l=l->next)
			checkgoto(l->n, lab->def);
	}
}

static void
checkgoto(Node *from, Node *to)
{
	int nf, nt;
	Sym *block, *dcl, *fs, *ts;
	int lno;

	if(from->sym == to->sym)
		return;

	nf = 0;
	for(fs=from->sym; fs; fs=fs->link)
		nf++;
	nt = 0;
	for(fs=to->sym; fs; fs=fs->link)
		nt++;
	fs = from->sym;
	for(; nf > nt; nf--)
		fs = fs->link;
	if(fs != to->sym) {
		lno = lineno;
		setlineno(from);

		// decide what to complain about.
		// prefer to complain about 'into block' over declarations,
		// so scan backward to find most recent block or else dcl.
		block = S;
		dcl = S;
		ts = to->sym;
		for(; nt > nf; nt--) {
			if(ts->pkg == nil)
				block = ts;
			else
				dcl = ts;
			ts = ts->link;
		}
		while(ts != fs) {
			if(ts->pkg == nil)
				block = ts;
			else
				dcl = ts;
			ts = ts->link;
			fs = fs->link;
		}

		if(block)
			yyerror("goto %S jumps into block starting at %L", from->left->sym, block->lastlineno);
		else
			yyerror("goto %S jumps over declaration of %S at %L", from->left->sym, dcl, dcl->lastlineno);
		lineno = lno;
	}
}

static Label*
stmtlabel(Node *n)
{
	Label *lab;

	if(n->sym != S)
	if((lab = n->sym->label) != L)
	if(lab->def != N)
	if(lab->def->defn == n)
		return lab;
	return L;
}

/*
 * compile statements
 */
void
genlist(NodeList *l)
{
	for(; l; l=l->next)
		gen(l->n);
}

void
gen(Node *n)
{
	int32 lno;
	Prog *scontin, *sbreak;
	Prog *p1, *p2, *p3;
	Label *lab;
	int32 wasregalloc;

//dump("gen", n);

	lno = setlineno(n);
	wasregalloc = anyregalloc();

	if(n == N)
		goto ret;

	if(n->ninit)
		genlist(n->ninit);

	setlineno(n);

	switch(n->op) {
	default:
		fatal("gen: unknown op %+hN", n);
		break;

	case OCASE:
	case OFALL:
	case OXCASE:
	case OXFALL:
	case ODCLCONST:
	case ODCLFUNC:
	case ODCLTYPE:
		break;

	case OEMPTY:
		break;

	case OBLOCK:
		genlist(n->list);
		break;

	case OLABEL:
		if(isblanksym(n->left->sym))
			break;
		
		lab = newlab(n);

		// if there are pending gotos, resolve them all to the current pc.
		for(p1=lab->gotopc; p1; p1=p2) {
			p2 = unpatch(p1);
			patch(p1, pc);
		}
		lab->gotopc = P;
		if(lab->labelpc == P)
			lab->labelpc = pc;

		if(n->defn) {
			switch(n->defn->op) {
			case OFOR:
			case OSWITCH:
			case OSELECT:
				// so stmtlabel can find the label
				n->defn->sym = lab->sym;
			}
		}
		break;

	case OGOTO:
		// if label is defined, emit jump to it.
		// otherwise save list of pending gotos in lab->gotopc.
		// the list is linked through the normal jump target field
		// to avoid a second list.  (the jumps are actually still
		// valid code, since they're just going to another goto
		// to the same label.  we'll unwind it when we learn the pc
		// of the label in the OLABEL case above.)
		lab = newlab(n);
		if(lab->labelpc != P)
			gjmp(lab->labelpc);
		else
			lab->gotopc = gjmp(lab->gotopc);
		break;

	case OBREAK:
		if(n->left != N) {
			lab = n->left->sym->label;
			if(lab == L) {
				yyerror("break label not defined: %S", n->left->sym);
				break;
			}
			lab->used = 1;
			if(lab->breakpc == P) {
				yyerror("invalid break label %S", n->left->sym);
				break;
			}
			gjmp(lab->breakpc);
			break;
		}
		if(breakpc == P) {
			yyerror("break is not in a loop");
			break;
		}
		gjmp(breakpc);
		break;

	case OCONTINUE:
		if(n->left != N) {
			lab = n->left->sym->label;
			if(lab == L) {
				yyerror("continue label not defined: %S", n->left->sym);
				break;
			}
			lab->used = 1;
			if(lab->continpc == P) {
				yyerror("invalid continue label %S", n->left->sym);
				break;
			}
			gjmp(lab->continpc);
			break;
		}
		if(continpc == P) {
			yyerror("continue is not in a loop");
			break;
		}
		gjmp(continpc);
		break;

	case OFOR:
		sbreak = breakpc;
		p1 = gjmp(P);			//		goto test
		breakpc = gjmp(P);		// break:	goto done
		scontin = continpc;
		continpc = pc;

		// define break and continue labels
		if((lab = stmtlabel(n)) != L) {
			lab->breakpc = breakpc;
			lab->continpc = continpc;
		}
		gen(n->nincr);				// contin:	incr
		patch(p1, pc);				// test:
		bgen(n->ntest, 0, -1, breakpc);		//		if(!test) goto break
		genlist(n->nbody);				//		body
		gjmp(continpc);
		patch(breakpc, pc);			// done:
		continpc = scontin;
		breakpc = sbreak;
		if(lab) {
			lab->breakpc = P;
			lab->continpc = P;
		}
		break;

	case OIF:
		p1 = gjmp(P);			//		goto test
		p2 = gjmp(P);			// p2:		goto else
		patch(p1, pc);				// test:
		bgen(n->ntest, 0, -n->likely, p2);		//		if(!test) goto p2
		genlist(n->nbody);				//		then
		p3 = gjmp(P);			//		goto done
		patch(p2, pc);				// else:
		genlist(n->nelse);				//		else
		patch(p3, pc);				// done:
		break;

	case OSWITCH:
		sbreak = breakpc;
		p1 = gjmp(P);			//		goto test
		breakpc = gjmp(P);		// break:	goto done

		// define break label
		if((lab = stmtlabel(n)) != L)
			lab->breakpc = breakpc;

		patch(p1, pc);				// test:
		genlist(n->nbody);				//		switch(test) body
		patch(breakpc, pc);			// done:
		breakpc = sbreak;
		if(lab != L)
			lab->breakpc = P;
		break;

	case OSELECT:
		sbreak = breakpc;
		p1 = gjmp(P);			//		goto test
		breakpc = gjmp(P);		// break:	goto done

		// define break label
		if((lab = stmtlabel(n)) != L)
			lab->breakpc = breakpc;

		patch(p1, pc);				// test:
		genlist(n->nbody);				//		select() body
		patch(breakpc, pc);			// done:
		breakpc = sbreak;
		if(lab != L)
			lab->breakpc = P;
		break;

	case OASOP:
		cgen_asop(n);
		break;

	case ODCL:
		cgen_dcl(n->left);
		break;

	case OAS:
		if(gen_as_init(n))
			break;
		cgen_as(n->left, n->right);
		break;

	case OCALLMETH:
		cgen_callmeth(n, 0);
		break;

	case OCALLINTER:
		cgen_callinter(n, N, 0);
		break;

	case OCALLFUNC:
		cgen_call(n, 0);
		break;

	case OPROC:
		cgen_proc(n, 1);
		break;

	case ODEFER:
		cgen_proc(n, 2);
		break;

	case ORETURN:
	case ORETJMP:
		cgen_ret(n);
		break;
	
	case OCHECKNIL:
		cgen_checknil(n->left);
		break;
	
	case OVARKILL:
		gvarkill(n->left);
		break;
	}

ret:
	if(anyregalloc() != wasregalloc) {
		dump("node", n);
		fatal("registers left allocated");
	}

	lineno = lno;
}

/*
 * generate call to non-interface method
 *	proc=0	normal call
 *	proc=1	goroutine run in new proc
 *	proc=2	defer call save away stack
 */
void
cgen_callmeth(Node *n, int proc)
{
	Node n2;
	Node *l;

	// generate a rewrite in n2 for the method call
	// (p.f)(...) goes to (f)(p,...)

	l = n->left;
	if(l->op != ODOTMETH)
		fatal("cgen_callmeth: not dotmethod: %N");

	n2 = *n;
	n2.op = OCALLFUNC;
	n2.left = l->right;
	n2.left->type = l->type;

	if(n2.left->op == ONAME)
		n2.left->class = PFUNC;
	cgen_call(&n2, proc);
}

/*
 * generate code to start new proc running call n.
 */
static void
cgen_proc(Node *n, int proc)
{
	switch(n->left->op) {
	default:
		fatal("cgen_proc: unknown call %O", n->left->op);

	case OCALLMETH:
		cgen_callmeth(n->left, proc);
		break;

	case OCALLINTER:
		cgen_callinter(n->left, N, proc);
		break;

	case OCALLFUNC:
		cgen_call(n->left, proc);
		break;
	}

}

/*
 * generate declaration.
 * have to allocate heap copy
 * for escaped variables.
 */
static void
cgen_dcl(Node *n)
{
	if(debug['g'])
		dump("\ncgen-dcl", n);
	if(n->op != ONAME) {
		dump("cgen_dcl", n);
		fatal("cgen_dcl");
	}
	if(!(n->class & PHEAP))
		return;
	if(n->alloc == nil)
		n->alloc = callnew(n->type);
	cgen_as(n->heapaddr, n->alloc);
}

/*
 * generate discard of value
 */
static void
cgen_discard(Node *nr)
{
	Node tmp;

	if(nr == N)
		return;

	switch(nr->op) {
	case ONAME:
		if(!(nr->class & PHEAP) && nr->class != PEXTERN && nr->class != PFUNC && nr->class != PPARAMREF)
			gused(nr);
		break;

	// unary
	case OADD:
	case OAND:
	case ODIV:
	case OEQ:
	case OGE:
	case OGT:
	case OLE:
	case OLSH:
	case OLT:
	case OMOD:
	case OMUL:
	case ONE:
	case OOR:
	case ORSH:
	case OSUB:
	case OXOR:
		cgen_discard(nr->left);
		cgen_discard(nr->right);
		break;

	// binary
	case OCAP:
	case OCOM:
	case OLEN:
	case OMINUS:
	case ONOT:
	case OPLUS:
		cgen_discard(nr->left);
		break;
	
	case OIND:
		cgen_checknil(nr->left);
		break;

	// special enough to just evaluate
	default:
		tempname(&tmp, nr->type);
		cgen_as(&tmp, nr);
		gused(&tmp);
	}
}

/*
 * clearslim generates code to zero a slim node.
 */
void
clearslim(Node *n)
{
	Node z;
	Mpflt zero;

	memset(&z, 0, sizeof(z));
	z.op = OLITERAL;
	z.type = n->type;
	z.addable = 1;

	switch(simtype[n->type->etype]) {
	case TCOMPLEX64:
	case TCOMPLEX128:
		z.val.u.cval = mal(sizeof(*z.val.u.cval));
		mpmovecflt(&z.val.u.cval->real, 0.0);
		mpmovecflt(&z.val.u.cval->imag, 0.0);
		break;

	case TFLOAT32:
	case TFLOAT64:
		mpmovecflt(&zero, 0.0);
		z.val.ctype = CTFLT;
		z.val.u.fval = &zero;
		break;

	case TPTR32:
	case TPTR64:
	case TCHAN:
	case TMAP:
		z.val.ctype = CTNIL;
		break;

	case TBOOL:
		z.val.ctype = CTBOOL;
		break;

	case TINT8:
	case TINT16:
	case TINT32:
	case TINT64:
	case TUINT8:
	case TUINT16:
	case TUINT32:
	case TUINT64:
		z.val.ctype = CTINT;
		z.val.u.xval = mal(sizeof(*z.val.u.xval));
		mpmovecfix(z.val.u.xval, 0);
		break;

	default:
		fatal("clearslim called on type %T", n->type);
	}

	ullmancalc(&z);
	cgen(&z, n);
}

/*
 * generate assignment:
 *	nl = nr
 * nr == N means zero nl.
 */
void
cgen_as(Node *nl, Node *nr)
{
	Type *tl;

	if(debug['g']) {
		dump("cgen_as", nl);
		dump("cgen_as = ", nr);
	}

	while(nr != N && nr->op == OCONVNOP)
		nr = nr->left;

	if(nl == N || isblank(nl)) {
		cgen_discard(nr);
		return;
	}

	if(nr == N || isnil(nr)) {
		// externals and heaps should already be clear
		if(nr == N) {
			if(nl->class == PEXTERN)
				return;
			if(nl->class & PHEAP)
				return;
		}

		tl = nl->type;
		if(tl == T)
			return;
		if(isfat(tl)) {
			if(nl->op == ONAME)
				gvardef(nl);
			clearfat(nl);
			return;
		}
		clearslim(nl);
		return;
	}

	tl = nl->type;
	if(tl == T)
		return;

	cgen(nr, nl);
}

/*
 * generate:
 *	res = iface{typ, data}
 * n->left is typ
 * n->right is data
 */
void
cgen_eface(Node *n, Node *res)
{
	/* 
	 * the right node of an eface may contain function calls that uses res as an argument,
	 * so it's important that it is done first
	 */
	Node dst;
	Node *tmp;

	tmp = temp(types[tptr]);
	cgen(n->right, tmp);

	gvardef(res);

	dst = *res;
	dst.type = types[tptr];
	dst.xoffset += widthptr;
	cgen(tmp, &dst);

	dst.xoffset -= widthptr;
	cgen(n->left, &dst);
}

/*
 * generate:
 *	res = s[lo, hi];
 * n->left is s
 * n->list is (cap(s)-lo(TUINT), hi-lo(TUINT)[, lo*width(TUINTPTR)])
 * caller (cgen) guarantees res is an addable ONAME.
 *
 * called for OSLICE, OSLICE3, OSLICEARR, OSLICE3ARR, OSLICESTR.
 */
void
cgen_slice(Node *n, Node *res)
{
	Node src, dst, *cap, *len, *offs, *add, *base;

	cap = n->list->n;
	len = n->list->next->n;
	offs = N;
	if(n->list->next->next)
		offs = n->list->next->next->n;

	// evaluate base pointer first, because it is the only
	// possibly complex expression. once that is evaluated
	// and stored, updating the len and cap can be done
	// without making any calls, so without doing anything that
	// might cause preemption or garbage collection.
	// this makes the whole slice update atomic as far as the
	// garbage collector can see.
	
	base = temp(types[TUINTPTR]);

	if(isnil(n->left)) {
		tempname(&src, n->left->type);
		cgen(n->left, &src);
	} else
		src = *n->left;
	if(n->op == OSLICE || n->op == OSLICE3 || n->op == OSLICESTR)
		src.xoffset += Array_array;
	src.type = types[TUINTPTR];

	if(n->op == OSLICEARR || n->op == OSLICE3ARR) {
		if(!isptr[n->left->type->etype])
			fatal("slicearr is supposed to work on pointer: %+N\n", n);
		cgen(&src, base);
		cgen_checknil(base);
		if(offs != N) {
			add = nod(OADD, base, offs);
			typecheck(&add, Erv);
			cgen(add, base);
		}
	} else if(offs == N) {
		cgen(&src, base);
	} else {
		add = nod(OADD, &src, offs);
		typecheck(&add, Erv);
		cgen(add, base);
	}
	
	// committed to the update
	gvardef(res);

	// dst.array = src.array  [ + lo *width ]
	dst = *res;
	dst.xoffset += Array_array;
	dst.type = types[TUINTPTR];
	
	cgen(base, &dst);

	// dst.len = hi [ - lo ]
	dst = *res;
	dst.xoffset += Array_nel;
	dst.type = types[simtype[TUINT]];
	cgen(len, &dst);

	if(n->op != OSLICESTR) {
		// dst.cap = cap [ - lo ]
		dst = *res;
		dst.xoffset += Array_cap;
		dst.type = types[simtype[TUINT]];
		cgen(cap, &dst);
	}
}

/*
 * gather series of offsets
 * >=0 is direct addressed field
 * <0 is pointer to next field (+1)
 */
int
dotoffset(Node *n, int64 *oary, Node **nn)
{
	int i;

	switch(n->op) {
	case ODOT:
		if(n->xoffset == BADWIDTH) {
			dump("bad width in dotoffset", n);
			fatal("bad width in dotoffset");
		}
		i = dotoffset(n->left, oary, nn);
		if(i > 0) {
			if(oary[i-1] >= 0)
				oary[i-1] += n->xoffset;
			else
				oary[i-1] -= n->xoffset;
			break;
		}
		if(i < 10)
			oary[i++] = n->xoffset;
		break;

	case ODOTPTR:
		if(n->xoffset == BADWIDTH) {
			dump("bad width in dotoffset", n);
			fatal("bad width in dotoffset");
		}
		i = dotoffset(n->left, oary, nn);
		if(i < 10)
			oary[i++] = -(n->xoffset+1);
		break;

	default:
		*nn = n;
		return 0;
	}
	if(i >= 10)
		*nn = N;
	return i;
}

/*
 * make a new off the books
 */
void
tempname(Node *nn, Type *t)
{
	Node *n;
	Sym *s;

	if(curfn == N)
		fatal("no curfn for tempname");

	if(t == T) {
		yyerror("tempname called with nil type");
		t = types[TINT32];
	}

	// give each tmp a different name so that there
	// a chance to registerizer them
	snprint(namebuf, sizeof(namebuf), "autotmp_%.4d", statuniqgen);
	statuniqgen++;
	s = lookup(namebuf);
	n = nod(ONAME, N, N);
	n->sym = s;
	s->def = n;
	n->type = t;
	n->class = PAUTO;
	n->addable = 1;
	n->ullman = 1;
	n->esc = EscNever;
	n->curfn = curfn;
	curfn->dcl = list(curfn->dcl, n);

	dowidth(t);
	n->xoffset = 0;
	*nn = *n;
}

Node*
temp(Type *t)
{
	Node *n;
	
	n = nod(OXXX, N, N);
	tempname(n, t);
	n->sym->def->used = 1;
	return n->orig;
}
