// 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.

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

static	Node*	walkprint(Node*, NodeList**, int);
static	Node*	mapfn(char*, Type*);
static	Node*	mapfndel(char*, Type*);
static	Node*	ascompatee1(int, Node*, Node*, NodeList**);
static	NodeList*	ascompatee(int, NodeList*, NodeList*, NodeList**);
static	NodeList*	ascompatet(int, NodeList*, Type**, int, NodeList**);
static	NodeList*	ascompatte(int, Node*, int, Type**, NodeList*, int, NodeList**);
static	Node*	convas(Node*, NodeList**);
static	void	heapmoves(void);
static	NodeList*	paramstoheap(Type **argin, int out);
static	NodeList*	reorder1(NodeList*);
static	NodeList*	reorder3(NodeList*);
static	Node*	addstr(Node*, NodeList**);
static	Node*	appendslice(Node*, NodeList**);
static	Node*	append(Node*, NodeList**);
static	void	walkcompare(Node**, NodeList**);

// can this code branch reach the end
// without an unconditional RETURN
// this is hard, so it is conservative
static int
walkret(NodeList *l)
{
	Node *n;

loop:
	while(l && l->next)
		l = l->next;
	if(l == nil)
		return 1;

	// at this point, we have the last
	// statement of the function
	n = l->n;
	switch(n->op) {
	case OBLOCK:
		l = n->list;
		goto loop;

	case OGOTO:
	case ORETURN:
	case OPANIC:
		return 0;
		break;
	}

	// all other statements
	// will flow to the end
	return 1;
}

void
walk(Node *fn)
{
	char s[50];
	NodeList *l;
	int lno;

	curfn = fn;

	if(debug['W']) {
		snprint(s, sizeof(s), "\nbefore %S", curfn->nname->sym);
		dumplist(s, curfn->nbody);
	}
	if(curfn->type->outtuple)
		if(walkret(curfn->nbody))
			yyerror("function ends without a return statement");

	lno = lineno;

	// Final typecheck for any unused variables.
	// It's hard to be on the heap when not-used, but best to be consistent about &~PHEAP here and below.
	for(l=fn->dcl; l; l=l->next)
		if(l->n->op == ONAME && (l->n->class&~PHEAP) == PAUTO)
			typecheck(&l->n, Erv | Easgn);

	// Propagate the used flag for typeswitch variables up to the NONAME in it's definition.
	for(l=fn->dcl; l; l=l->next)
		if(l->n->op == ONAME && (l->n->class&~PHEAP) == PAUTO && l->n->defn && l->n->defn->op == OTYPESW && l->n->used)
			l->n->defn->left->used++;
	
	for(l=fn->dcl; l; l=l->next) {
		if(l->n->op != ONAME || (l->n->class&~PHEAP) != PAUTO || l->n->sym->name[0] == '&' || l->n->used)
			continue;
		if(l->n->defn && l->n->defn->op == OTYPESW) {
			if(l->n->defn->left->used)
				continue;
			lineno = l->n->defn->left->lineno;
			yyerror("%S declared and not used", l->n->sym);
			l->n->defn->left->used = 1; // suppress repeats
		} else {
			lineno = l->n->lineno;
			yyerror("%S declared and not used", l->n->sym);
		}
	}	

	lineno = lno;
	if(nerrors != 0)
		return;
	walkstmtlist(curfn->nbody);
	if(debug['W']) {
		snprint(s, sizeof(s), "after walk %S", curfn->nname->sym);
		dumplist(s, curfn->nbody);
	}
	heapmoves();
	if(debug['W'] && curfn->enter != nil) {
		snprint(s, sizeof(s), "enter %S", curfn->nname->sym);
		dumplist(s, curfn->enter);
	}
}


void
walkstmtlist(NodeList *l)
{
	for(; l; l=l->next)
		walkstmt(&l->n);
}

static int
samelist(NodeList *a, NodeList *b)
{
	for(; a && b; a=a->next, b=b->next)
		if(a->n != b->n)
			return 0;
	return a == b;
}

static int
paramoutheap(Node *fn)
{
	NodeList *l;

	for(l=fn->dcl; l; l=l->next) {
		switch(l->n->class) {
		case PPARAMOUT:
		case PPARAMOUT|PHEAP:
			return l->n->addrtaken;
		case PAUTO:
		case PAUTO|PHEAP:
			// stop early - parameters are over
			return 0;
		}
	}
	return 0;
}

void
walkstmt(Node **np)
{
	NodeList *init;
	NodeList *ll, *rl;
	int cl;
	Node *n, *f;

	n = *np;
	if(n == N)
		return;

	setlineno(n);

	walkstmtlist(n->ninit);

	switch(n->op) {
	default:
		if(n->op == ONAME)
			yyerror("%S is not a top level statement", n->sym);
		else
			yyerror("%O is not a top level statement", n->op);
		dump("nottop", n);
		break;

	case OASOP:
	case OAS:
	case OAS2:
	case OAS2DOTTYPE:
	case OAS2RECV:
	case OAS2FUNC:
	case OAS2MAPR:
	case OCLOSE:
	case OCOPY:
	case OCALLMETH:
	case OCALLINTER:
	case OCALL:
	case OCALLFUNC:
	case ODELETE:
	case OSEND:
	case ORECV:
	case OPRINT:
	case OPRINTN:
	case OPANIC:
	case OEMPTY:
	case ORECOVER:
		if(n->typecheck == 0)
			fatal("missing typecheck: %+N", n);
		init = n->ninit;
		n->ninit = nil;
		walkexpr(&n, &init);
		addinit(&n, init);
		break;

	case OBREAK:
	case ODCL:
	case OCONTINUE:
	case OFALL:
	case OGOTO:
	case OLABEL:
	case ODCLCONST:
	case ODCLTYPE:
		break;

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

	case OXCASE:
		yyerror("case statement out of place");
		n->op = OCASE;
	case OCASE:
		walkstmt(&n->right);
		break;

	case ODEFER:
		hasdefer = 1;
		switch(n->left->op) {
		case OPRINT:
		case OPRINTN:
			walkexprlist(n->left->list, &n->ninit);
			n->left = walkprint(n->left, &n->ninit, 1);
			break;
		default:
			walkexpr(&n->left, &n->ninit);
			break;
		}
		break;

	case OFOR:
		if(n->ntest != N) {
			walkstmtlist(n->ntest->ninit);
			init = n->ntest->ninit;
			n->ntest->ninit = nil;
			walkexpr(&n->ntest, &init);
			addinit(&n->ntest, init);
		}
		walkstmt(&n->nincr);
		walkstmtlist(n->nbody);
		break;

	case OIF:
		walkexpr(&n->ntest, &n->ninit);
		walkstmtlist(n->nbody);
		walkstmtlist(n->nelse);
		break;

	case OPROC:
		switch(n->left->op) {
		case OPRINT:
		case OPRINTN:
			walkexprlist(n->left->list, &n->ninit);
			n->left = walkprint(n->left, &n->ninit, 1);
			break;
		default:
			walkexpr(&n->left, &n->ninit);
			break;
		}
		break;

	case ORETURN:
		walkexprlist(n->list, &n->ninit);
		if(n->list == nil)
			break;
		if((curfn->type->outnamed && count(n->list) > 1) || paramoutheap(curfn)) {
			// assign to the function out parameters,
			// so that reorder3 can fix up conflicts
			rl = nil;
			for(ll=curfn->dcl; ll != nil; ll=ll->next) {
				cl = ll->n->class & ~PHEAP;
				if(cl == PAUTO)
					break;
				if(cl == PPARAMOUT)
					rl = list(rl, ll->n);
			}
			if(samelist(rl, n->list)) {
				// special return in disguise
				n->list = nil;
				break;
			}
			if(count(n->list) == 1 && count(rl) > 1) {
				// OAS2FUNC in disguise
				f = n->list->n;
				if(f->op != OCALLFUNC && f->op != OCALLMETH && f->op != OCALLINTER)
					fatal("expected return of call, have %N", f);
				n->list = concat(list1(f), ascompatet(n->op, rl, &f->type, 0, &n->ninit));
				break;
			}

			// move function calls out, to make reorder3's job easier.
			walkexprlistsafe(n->list, &n->ninit);
			ll = ascompatee(n->op, rl, n->list, &n->ninit);
			n->list = reorder3(ll);
			break;
		}
		ll = ascompatte(n->op, nil, 0, getoutarg(curfn->type), n->list, 1, &n->ninit);
		n->list = ll;
		break;

	case OSELECT:
		walkselect(n);
		break;

	case OSWITCH:
		walkswitch(n);
		break;

	case ORANGE:
		walkrange(n);
		break;

	case OXFALL:
		yyerror("fallthrough statement out of place");
		n->op = OFALL;
		break;
	}

	if(n->op == ONAME)
		fatal("walkstmt ended up with name: %+N", n);
	
	*np = n;
}


/*
 * walk the whole tree of the body of an
 * expression or simple statement.
 * the types expressions are calculated.
 * compile-time constants are evaluated.
 * complex side effects like statements are appended to init
 */

void
walkexprlist(NodeList *l, NodeList **init)
{
	for(; l; l=l->next)
		walkexpr(&l->n, init);
}

void
walkexprlistsafe(NodeList *l, NodeList **init)
{
	for(; l; l=l->next) {
		l->n = safeexpr(l->n, init);
		walkexpr(&l->n, init);
	}
}

void
walkexpr(Node **np, NodeList **init)
{
	Node *r, *l, *var, *a;
	NodeList *ll, *lr, *lpost;
	Type *t;
	int et;
	int64 v, v1, v2, len;
	int32 lno;
	Node *n, *fn;
	char buf[100], *p;

	n = *np;

	if(n == N)
		return;

	if(init == &n->ninit) {
		// not okay to use n->ninit when walking n,
		// because we might replace n with some other node
		// and would lose the init list.
		fatal("walkexpr init == &n->ninit");
	}

	if(n->ninit != nil) {
		walkstmtlist(n->ninit);
		*init = concat(*init, n->ninit);
		n->ninit = nil;
	}

	// annoying case - not typechecked
	if(n->op == OKEY) {
		walkexpr(&n->left, init);
		walkexpr(&n->right, init);
		return;
	}

	lno = setlineno(n);

	if(debug['w'] > 1)
		dump("walk-before", n);

	if(n->typecheck != 1)
		fatal("missed typecheck: %+N\n", n);

	switch(n->op) {
	default:
		dump("walk", n);
		fatal("walkexpr: switch 1 unknown op %N", n);
		break;

	case OTYPE:
	case ONONAME:
	case OINDREG:
	case OEMPTY:
		goto ret;

	case ONOT:
	case OMINUS:
	case OPLUS:
	case OCOM:
	case OREAL:
	case OIMAG:
	case ODOT:
	case ODOTPTR:
	case ODOTMETH:
	case ODOTINTER:
	case OIND:
		walkexpr(&n->left, init);
		goto ret;

	case OITAB:
		walkexpr(&n->left, init);
		goto ret;

	case OLEN:
	case OCAP:
		walkexpr(&n->left, init);

		// replace len(*[10]int) with 10.
		// delayed until now to preserve side effects.
		t = n->left->type;
		if(isptr[t->etype])
			t = t->type;
		if(isfixedarray(t)) {
			safeexpr(n->left, init);
			nodconst(n, n->type, t->bound);
			n->typecheck = 1;
		}
		goto ret;

	case OLSH:
	case ORSH:
	case OAND:
	case OOR:
	case OXOR:
	case OSUB:
	case OMUL:
	case OLT:
	case OLE:
	case OGE:
	case OGT:
	case OADD:
	case OCOMPLEX:
		walkexpr(&n->left, init);
		walkexpr(&n->right, init);
		goto ret;

	case OEQ:
	case ONE:
		walkexpr(&n->left, init);
		walkexpr(&n->right, init);
		walkcompare(&n, init);
		goto ret;

	case OANDAND:
	case OOROR:
		walkexpr(&n->left, init);
		// cannot put side effects from n->right on init,
		// because they cannot run before n->left is checked.
		// save elsewhere and store on the eventual n->right.
		ll = nil;
		walkexpr(&n->right, &ll);
		addinit(&n->right, ll);
		goto ret;

	case OPRINT:
	case OPRINTN:
		walkexprlist(n->list, init);
		n = walkprint(n, init, 0);
		goto ret;

	case OPANIC:
		n = mkcall("panic", T, init, n->left);
		goto ret;

	case ORECOVER:
		n = mkcall("recover", n->type, init, nod(OADDR, nodfp, N));
		goto ret;

	case OLITERAL:
		n->addable = 1;
		goto ret;

	case ONAME:
		if(!(n->class & PHEAP) && n->class != PPARAMREF)
			n->addable = 1;
		goto ret;

	case OCALLINTER:
		t = n->left->type;
		if(n->list && n->list->n->op == OAS)
			goto ret;
		walkexpr(&n->left, init);
		walkexprlist(n->list, init);
		ll = ascompatte(n->op, n, n->isddd, getinarg(t), n->list, 0, init);
		n->list = reorder1(ll);
		goto ret;

	case OCALLFUNC:
		t = n->left->type;
		if(n->list && n->list->n->op == OAS)
			goto ret;

		if(n->left->op == OCLOSURE) {
			walkcallclosure(n, init);
			t = n->left->type;
		}

		walkexpr(&n->left, init);
		walkexprlist(n->list, init);

		ll = ascompatte(n->op, n, n->isddd, getinarg(t), n->list, 0, init);
		n->list = reorder1(ll);
		goto ret;

	case OCALLMETH:
		t = n->left->type;
		if(n->list && n->list->n->op == OAS)
			goto ret;
		walkexpr(&n->left, init);
		walkexprlist(n->list, init);
		ll = ascompatte(n->op, n, 0, getthis(t), list1(n->left->left), 0, init);
		lr = ascompatte(n->op, n, n->isddd, getinarg(t), n->list, 0, init);
		ll = concat(ll, lr);
		n->left->left = N;
		ullmancalc(n->left);
		n->list = reorder1(ll);
		goto ret;

	case OAS:
		*init = concat(*init, n->ninit);
		n->ninit = nil;
		walkexpr(&n->left, init);
		n->left = safeexpr(n->left, init);

		if(oaslit(n, init))
			goto ret;

		walkexpr(&n->right, init);
		if(n->left != N && n->right != N) {
			r = convas(nod(OAS, n->left, n->right), init);
			r->dodata = n->dodata;
			n = r;
		}

		goto ret;

	case OAS2:
		*init = concat(*init, n->ninit);
		n->ninit = nil;
		walkexprlistsafe(n->list, init);
		walkexprlistsafe(n->rlist, init);
		ll = ascompatee(OAS, n->list, n->rlist, init);
		ll = reorder3(ll);
		n = liststmt(ll);
		goto ret;

	case OAS2FUNC:
	as2func:
		// a,b,... = fn()
		*init = concat(*init, n->ninit);
		n->ninit = nil;
		r = n->rlist->n;
		walkexprlistsafe(n->list, init);
		walkexpr(&r, init);
		l = n->list->n;

		// all the really hard stuff - explicit function calls and so on -
		// is gone, but map assignments remain.
		// if there are map assignments here, assign via
		// temporaries, because ascompatet assumes
		// the targets can be addressed without function calls
		// and map index has an implicit one.
		lpost = nil;
		if(l->op == OINDEXMAP) {
			var = temp(l->type);
			n->list->n = var;
			a = nod(OAS, l, var);
			typecheck(&a, Etop);
			lpost = list(lpost, a);
		}
		l = n->list->next->n;
		if(l->op == OINDEXMAP) {
			var = temp(l->type);
			n->list->next->n = var;
			a = nod(OAS, l, var);
			typecheck(&a, Etop);
			lpost = list(lpost, a);
		}
		ll = ascompatet(n->op, n->list, &r->type, 0, init);
		walkexprlist(lpost, init);
		n = liststmt(concat(concat(list1(r), ll), lpost));
		goto ret;

	case OAS2RECV:
		*init = concat(*init, n->ninit);
		n->ninit = nil;
		r = n->rlist->n;
		walkexprlistsafe(n->list, init);
		walkexpr(&r->left, init);
		fn = chanfn("chanrecv2", 2, r->left->type);
		r = mkcall1(fn, getoutargx(fn->type), init, typename(r->left->type), r->left);
		n->rlist->n = r;
		n->op = OAS2FUNC;
		goto as2func;

	case OAS2MAPR:
		// a,b = m[i];
		*init = concat(*init, n->ninit);
		n->ninit = nil;
		r = n->rlist->n;
		walkexprlistsafe(n->list, init);
		walkexpr(&r->left, init);
		fn = mapfn("mapaccess2", r->left->type);
		r = mkcall1(fn, getoutargx(fn->type), init, typename(r->left->type), r->left, r->right);
		n->rlist = list1(r);
		n->op = OAS2FUNC;
		goto as2func;

	case ODELETE:
		*init = concat(*init, n->ninit);
		n->ninit = nil;
		l = n->list->n;
		r = n->list->next->n;
		t = l->type;
		n = mkcall1(mapfndel("mapdelete", t), t->down, init, typename(t), l, r);
		goto ret;

	case OAS2DOTTYPE:
		// a,b = i.(T)
		*init = concat(*init, n->ninit);
		n->ninit = nil;
		r = n->rlist->n;
		walkexprlistsafe(n->list, init);
		r->op = ODOTTYPE2;
		walkexpr(&r, init);
		ll = ascompatet(n->op, n->list, &r->type, 0, init);
		n = liststmt(concat(list1(r), ll));
		goto ret;

	case ODOTTYPE:
	case ODOTTYPE2:
		// Build name of function: assertI2E2 etc.
		strcpy(buf, "assert");
		p = buf+strlen(buf);
		if(isnilinter(n->left->type))
			*p++ = 'E';
		else
			*p++ = 'I';
		*p++ = '2';
		if(isnilinter(n->type))
			*p++ = 'E';
		else if(isinter(n->type))
			*p++ = 'I';
		else
			*p++ = 'T';
		if(n->op == ODOTTYPE2)
			*p++ = '2';
		*p = '\0';

		fn = syslook(buf, 1);
		ll = list1(typename(n->type));
		ll = list(ll, n->left);
		argtype(fn, n->left->type);
		argtype(fn, n->type);
		n = nod(OCALL, fn, N);
		n->list = ll;
		typecheck(&n, Erv | Efnstruct);
		walkexpr(&n, init);
		goto ret;

	case OCONVIFACE:
		// Build name of function: convI2E etc.
		// Not all names are possible
		// (e.g., we'll never generate convE2E or convE2I).
		walkexpr(&n->left, init);
		strcpy(buf, "conv");
		p = buf+strlen(buf);
		if(isnilinter(n->left->type))
			*p++ = 'E';
		else if(isinter(n->left->type))
			*p++ = 'I';
		else
			*p++ = 'T';
		*p++ = '2';
		if(isnilinter(n->type))
			*p++ = 'E';
		else
			*p++ = 'I';
		*p = '\0';

		fn = syslook(buf, 1);
		ll = nil;
		if(!isinter(n->left->type))
			ll = list(ll, typename(n->left->type));
		if(!isnilinter(n->type))
			ll = list(ll, typename(n->type));
		ll = list(ll, n->left);
		argtype(fn, n->left->type);
		argtype(fn, n->type);
		dowidth(fn->type);
		n = nod(OCALL, fn, N);
		n->list = ll;
		typecheck(&n, Erv);
		walkexpr(&n, init);
		goto ret;

	case OCONV:
	case OCONVNOP:
		if(thechar == '5') {
			if(isfloat[n->left->type->etype]) {
				if(n->type->etype == TINT64) {
					n = mkcall("float64toint64", n->type, init, conv(n->left, types[TFLOAT64]));
					goto ret;
				}
				if(n->type->etype == TUINT64) {
					n = mkcall("float64touint64", n->type, init, conv(n->left, types[TFLOAT64]));
					goto ret;
				}
			}
			if(isfloat[n->type->etype]) {
				if(n->left->type->etype == TINT64) {
					n = mkcall("int64tofloat64", n->type, init, conv(n->left, types[TINT64]));
					goto ret;
				}
				if(n->left->type->etype == TUINT64) {
					n = mkcall("uint64tofloat64", n->type, init, conv(n->left, types[TUINT64]));
					goto ret;
				}
			}
		}
		walkexpr(&n->left, init);
		goto ret;

	case OASOP:
		if(n->etype == OANDNOT) {
			n->etype = OAND;
			n->right = nod(OCOM, n->right, N);
			typecheck(&n->right, Erv);
		}
		n->left = safeexpr(n->left, init);
		walkexpr(&n->left, init);
		l = n->left;
		walkexpr(&n->right, init);

		/*
		 * on 32-bit arch, rewrite 64-bit ops into l = l op r.
		 * on 386, rewrite float ops into l = l op r.
		 * everywhere, rewrite map ops into l = l op r.
		 * everywhere, rewrite string += into l = l op r.
		 * everywhere, rewrite complex /= into l = l op r.
		 * TODO(rsc): Maybe this rewrite should be done always?
		 */
		et = n->left->type->etype;
		if((widthptr == 4 && (et == TUINT64 || et == TINT64)) ||
		   (thechar == '8' && isfloat[et]) ||
		   l->op == OINDEXMAP ||
		   et == TSTRING ||
		   (iscomplex[et] && n->etype == ODIV)) {
			l = safeexpr(n->left, init);
			a = l;
			if(a->op == OINDEXMAP) {
				// map index has "lhs" bit set in a->etype.
				// make a copy so we can clear it on the rhs.
				a = nod(OXXX, N, N);
				*a = *l;
				a->etype = 0;
			}
			r = nod(OAS, l, nod(n->etype, a, n->right));
			typecheck(&r, Etop);
			walkexpr(&r, init);
			n = r;
		}
		goto ret;

	case OANDNOT:
		walkexpr(&n->left, init);
		walkexpr(&n->right, init);
		n->op = OAND;
		n->right = nod(OCOM, n->right, N);
		typecheck(&n->right, Erv);
		goto ret;

	case ODIV:
	case OMOD:
		walkexpr(&n->left, init);
		walkexpr(&n->right, init);
		/*
		 * rewrite complex div into function call.
		 */
		et = n->left->type->etype;
		if(iscomplex[et] && n->op == ODIV) {
			t = n->type;
			n = mkcall("complex128div", types[TCOMPLEX128], init,
				conv(n->left, types[TCOMPLEX128]),
				conv(n->right, types[TCOMPLEX128]));
			n = conv(n, t);
			goto ret;
		}
		/*
		 * rewrite div and mod into function calls
		 * on 32-bit architectures.
		 */
		if(widthptr > 4 || (et != TUINT64 && et != TINT64))
			goto ret;
		if(et == TINT64)
			strcpy(namebuf, "int64");
		else
			strcpy(namebuf, "uint64");
		if(n->op == ODIV)
			strcat(namebuf, "div");
		else
			strcat(namebuf, "mod");
		n = mkcall(namebuf, n->type, init,
			conv(n->left, types[et]), conv(n->right, types[et]));
		goto ret;

	case OINDEX:
		walkexpr(&n->left, init);
		walkexpr(&n->right, init);

		// if range of type cannot exceed static array bound,
		// disable bounds check
		if(isfixedarray(n->left->type))
		if(n->right->type->width < 4)
		if((1<<(8*n->right->type->width)) <= n->left->type->bound)
			n->etype = 1;

		if(isconst(n->left, CTSTR))
		if(n->right->type->width < 4)
		if((1<<(8*n->right->type->width)) <= n->left->val.u.sval->len)
			n->etype = 1;

		// check for static out of bounds
		if(isconst(n->right, CTINT) && !n->etype) {
			v = mpgetfix(n->right->val.u.xval);
			len = 1LL<<60;
			t = n->left->type;
			if(isconst(n->left, CTSTR))
				len = n->left->val.u.sval->len;
			if(t != T && isptr[t->etype])
				t = t->type;
			if(isfixedarray(t))
				len = t->bound;
			if(v < 0 || v >= (1LL<<31) || v >= len)
				yyerror("index out of bounds");
			else if(isconst(n->left, CTSTR)) {
				// replace "abc"[2] with 'b'.
				// delayed until now because "abc"[2] is not
				// an ideal constant.
				nodconst(n, n->type, n->left->val.u.sval->s[v]);
				n->typecheck = 1;
			}
		}
		goto ret;

	case OINDEXMAP:
		if(n->etype == 1)
			goto ret;

		t = n->left->type;
		n = mkcall1(mapfn("mapaccess1", t), t->type, init, typename(t), n->left, n->right);
		goto ret;

	case ORECV:
		walkexpr(&n->left, init);
		walkexpr(&n->right, init);
		n = mkcall1(chanfn("chanrecv1", 2, n->left->type), n->type, init, typename(n->left->type), n->left);
		goto ret;

	case OSLICE:
	case OSLICEARR:
		walkexpr(&n->left, init);
		n->left = safeexpr(n->left, init);
		walkexpr(&n->right->left, init);
		n->right->left = safeexpr(n->right->left, init);
		walkexpr(&n->right->right, init);
		n->right->right = safeexpr(n->right->right, init);

		len = 1LL<<60;
		t = n->left->type;
		if(t != T && isptr[t->etype])
			t = t->type;
		if(isfixedarray(t))
			len = t->bound;

		// check for static out of bounds
		// NOTE: v > len not v >= len.
		v1 = -1;
		v2 = -1;
		if(isconst(n->right->left, CTINT)) {
			v1 = mpgetfix(n->right->left->val.u.xval);
			if(v1 < 0 || v1 >= (1LL<<31) || v1 > len) {
				yyerror("slice index out of bounds");
				v1 = -1;
			}
		}
		if(isconst(n->right->right, CTINT)) {
			v2 = mpgetfix(n->right->right->val.u.xval);
			if(v2 < 0 || v2 >= (1LL<<31) || v2 > len) {
				yyerror("slice index out of bounds");
				v2 = -1;
			}
		}
		if(v1 >= 0 && v2 >= 0 && v1 > v2)
			yyerror("inverted slice range");

		if(n->op == OSLICEARR)
			goto slicearray;

		// dynamic slice
		// sliceslice(old []any, lb uint64, hb uint64, width uint64) (ary []any)
		// sliceslice1(old []any, lb uint64, width uint64) (ary []any)
		t = n->type;
		et = n->etype;
		if(n->right->left == N)
			l = nodintconst(0);
		else
			l = conv(n->right->left, types[TUINT64]);
		if(n->right->right != N) {
			fn = syslook("sliceslice", 1);
			argtype(fn, t->type);			// any-1
			argtype(fn, t->type);			// any-2
			n = mkcall1(fn, t, init,
				n->left,
				l,
				conv(n->right->right, types[TUINT64]),
				nodintconst(t->type->width));
		} else {
			fn = syslook("sliceslice1", 1);
			argtype(fn, t->type);			// any-1
			argtype(fn, t->type);			// any-2
			n = mkcall1(fn, t, init,
				n->left,
				l,
				nodintconst(t->type->width));
		}
		n->etype = et;	// preserve no-typecheck flag from OSLICE to the slice* call.
		goto ret;

	slicearray:
		// static slice
		// slicearray(old *any, uint64 nel, lb uint64, hb uint64, width uint64) (ary []any)
		t = n->type;
		fn = syslook("slicearray", 1);
		argtype(fn, n->left->type->type);	// any-1
		argtype(fn, t->type);			// any-2
		if(n->right->left == N)
			l = nodintconst(0);
		else
			l = conv(n->right->left, types[TUINT64]);
		if(n->right->right == N)
			r = nodintconst(n->left->type->type->bound);
		else
			r = conv(n->right->right, types[TUINT64]);
		n = mkcall1(fn, t, init,
			n->left, nodintconst(n->left->type->type->bound),
			l,
			r,
			nodintconst(t->type->width));
		goto ret;

	case OADDR:
		walkexpr(&n->left, init);
		goto ret;

	case ONEW:
		if(n->esc == EscNone && n->type->type->width < (1<<16)) {
			r = temp(n->type->type);
			r = nod(OAS, r, N);  // zero temp
			typecheck(&r, Etop);
			*init = list(*init, r);
			r = nod(OADDR, r->left, N);
			typecheck(&r, Erv);
			n = r;
		} else {
			n = callnew(n->type->type);
		}
		goto ret;

	case OCMPSTR:
		// If one argument to the comparison is an empty string,
		// comparing the lengths instead will yield the same result
		// without the function call.
		if((isconst(n->left, CTSTR) && n->left->val.u.sval->len == 0) ||
		   (isconst(n->right, CTSTR) && n->right->val.u.sval->len == 0)) {
			r = nod(n->etype, nod(OLEN, n->left, N), nod(OLEN, n->right, N));
			typecheck(&r, Erv);
			walkexpr(&r, init);
			r->type = n->type;
			n = r;
			goto ret;
		}

		// s + "badgerbadgerbadger" == "badgerbadgerbadger"
		if((n->etype == OEQ || n->etype == ONE) &&
		   isconst(n->right, CTSTR) &&
		   n->left->op == OADDSTR && isconst(n->left->right, CTSTR) &&
		   cmpslit(n->right, n->left->right) == 0) {
			r = nod(n->etype, nod(OLEN, n->left->left, N), nodintconst(0));
			typecheck(&r, Erv);
			walkexpr(&r, init);
			r->type = n->type;
			n = r;
			goto ret;
		}

		// prepare for rewrite below
		if(n->etype == OEQ || n->etype == ONE) {
			n->left = cheapexpr(n->left, init);
			n->right = cheapexpr(n->right, init);
		}

		// sys_cmpstring(s1, s2) :: 0
		r = mkcall("cmpstring", types[TINT], init,
			conv(n->left, types[TSTRING]),
			conv(n->right, types[TSTRING]));
		r = nod(n->etype, r, nodintconst(0));

		// quick check of len before full compare for == or !=
		if(n->etype == OEQ || n->etype == ONE) {
			if(n->etype == OEQ)
				r = nod(OANDAND, nod(OEQ, nod(OLEN, n->left, N), nod(OLEN, n->right, N)), r);
			else
				r = nod(OOROR, nod(ONE, nod(OLEN, n->left, N), nod(OLEN, n->right, N)), r);
			typecheck(&r, Erv);
			walkexpr(&r, nil);
		}
		typecheck(&r, Erv);
		if(n->type->etype != TBOOL) fatal("cmp %T", n->type);
		r->type = n->type;
		n = r;
		goto ret;

	case OADDSTR:
		n = addstr(n, init);
		goto ret;

	case OSLICESTR:
		// sys_slicestring(s, lb, hb)
		if(n->right->left == N)
			l = nodintconst(0);
		else
			l = conv(n->right->left, types[TINT]);
		if(n->right->right) {
			n = mkcall("slicestring", n->type, init,
				conv(n->left, types[TSTRING]),
				l,
				conv(n->right->right, types[TINT]));
		} else {
			n = mkcall("slicestring1", n->type, init,
				conv(n->left, types[TSTRING]),
				l);
		}
		goto ret;

	case OAPPEND:
		if(n->isddd) {
			if(istype(n->type->type, TUINT8) && istype(n->list->next->n->type, TSTRING))
				n = mkcall("appendstr", n->type, init, typename(n->type), n->list->n, n->list->next->n);
			else
				n = appendslice(n, init);
		}
		else
			n = append(n, init);
		goto ret;

	case OCOPY:
		if(n->right->type->etype == TSTRING)
			fn = syslook("slicestringcopy", 1);
		else
			fn = syslook("copy", 1);
		argtype(fn, n->left->type);
		argtype(fn, n->right->type);
		n = mkcall1(fn, n->type, init,
			n->left, n->right,
			nodintconst(n->left->type->type->width));
		goto ret;

	case OCLOSE:
		// cannot use chanfn - closechan takes any, not chan any
		fn = syslook("closechan", 1);
		argtype(fn, n->left->type);
		n = mkcall1(fn, T, init, n->left);
		goto ret;

	case OMAKECHAN:
		n = mkcall1(chanfn("makechan", 1, n->type), n->type, init,
			typename(n->type),
			conv(n->left, types[TINT64]));
		goto ret;

	case OMAKEMAP:
		t = n->type;

		fn = syslook("makemap", 1);
		argtype(fn, t->down);	// any-1
		argtype(fn, t->type);	// any-2

		n = mkcall1(fn, n->type, init,
			typename(n->type),
			conv(n->left, types[TINT64]));
		goto ret;

	case OMAKESLICE:
		// makeslice(t *Type, nel int64, max int64) (ary []any)
		l = n->left;
		r = n->right;
		if(r == nil)
			l = r = safeexpr(l, init);
		t = n->type;
		fn = syslook("makeslice", 1);
		argtype(fn, t->type);			// any-1
		n = mkcall1(fn, n->type, init,
			typename(n->type),
			conv(l, types[TINT64]),
			conv(r, types[TINT64]));
		goto ret;

	case ORUNESTR:
		// sys_intstring(v)
		n = mkcall("intstring", n->type, init,
			conv(n->left, types[TINT64]));
		goto ret;

	case OARRAYBYTESTR:
		// slicebytetostring([]byte) string;
		n = mkcall("slicebytetostring", n->type, init, n->left);
		goto ret;

	case OARRAYRUNESTR:
		// slicerunetostring([]rune) string;
		n = mkcall("slicerunetostring", n->type, init, n->left);
		goto ret;

	case OSTRARRAYBYTE:
		// stringtoslicebyte(string) []byte;
		n = mkcall("stringtoslicebyte", n->type, init, conv(n->left, types[TSTRING]));
		goto ret;

	case OSTRARRAYRUNE:
		// stringtoslicerune(string) []rune
		n = mkcall("stringtoslicerune", n->type, init, n->left);
		goto ret;

	case OCMPIFACE:
		// ifaceeq(i1 any-1, i2 any-2) (ret bool);
		if(!eqtype(n->left->type, n->right->type))
			fatal("ifaceeq %O %T %T", n->op, n->left->type, n->right->type);
		if(isnilinter(n->left->type))
			fn = syslook("efaceeq", 1);
		else
			fn = syslook("ifaceeq", 1);
		argtype(fn, n->right->type);
		argtype(fn, n->left->type);
		r = mkcall1(fn, n->type, init, n->left, n->right);
		if(n->etype == ONE)
			r = nod(ONOT, r, N);
		
		// check itable/type before full compare.
		if(n->etype == OEQ)
			r = nod(OANDAND, nod(OEQ, nod(OITAB, n->left, N), nod(OITAB, n->right, N)), r);
		else
			r = nod(OOROR, nod(ONE, nod(OITAB, n->left, N), nod(OITAB, n->right, N)), r);
		typecheck(&r, Erv);
		walkexpr(&r, nil);
		r->type = n->type;
		n = r;
		goto ret;

	case OARRAYLIT:
	case OMAPLIT:
	case OSTRUCTLIT:
	case OPTRLIT:
		var = temp(n->type);
		anylit(0, n, var, init);
		n = var;
		goto ret;

	case OSEND:
		n = mkcall1(chanfn("chansend1", 2, n->left->type), T, init, typename(n->left->type), n->left, n->right);
		goto ret;

	case OCLOSURE:
		n = walkclosure(n, init);
		goto ret;
	}
	fatal("missing switch %O", n->op);

ret:
	ullmancalc(n);

	if(debug['w'] && n != N)
		dump("walk", n);

	lineno = lno;
	*np = n;
}

static Node*
ascompatee1(int op, Node *l, Node *r, NodeList **init)
{
	USED(op);

	return convas(nod(OAS, l, r), init);
}

static NodeList*
ascompatee(int op, NodeList *nl, NodeList *nr, NodeList **init)
{
	NodeList *ll, *lr, *nn;

	/*
	 * check assign expression list to
	 * a expression list. called in
	 *	expr-list = expr-list
	 */

	// ensure order of evaluation for function calls
	for(ll=nl; ll; ll=ll->next)
		ll->n = safeexpr(ll->n, init);
	for(lr=nr; lr; lr=lr->next)
		lr->n = safeexpr(lr->n, init);

	nn = nil;
	for(ll=nl, lr=nr; ll && lr; ll=ll->next, lr=lr->next)
		nn = list(nn, ascompatee1(op, ll->n, lr->n, init));

	// cannot happen: caller checked that lists had same length
	if(ll || lr)
		yyerror("error in shape across %+H %O %+H", nl, op, nr);
	return nn;
}

/*
 * l is an lv and rt is the type of an rv
 * return 1 if this implies a function call
 * evaluating the lv or a function call
 * in the conversion of the types
 */
static int
fncall(Node *l, Type *rt)
{
	if(l->ullman >= UINF || l->op == OINDEXMAP)
		return 1;
	if(eqtype(l->type, rt))
		return 0;
	return 1;
}

static NodeList*
ascompatet(int op, NodeList *nl, Type **nr, int fp, NodeList **init)
{
	Node *l, *tmp, *a;
	NodeList *ll;
	Type *r;
	Iter saver;
	int ucount;
	NodeList *nn, *mm;

	USED(op);

	/*
	 * check assign type list to
	 * a expression list. called in
	 *	expr-list = func()
	 */
	r = structfirst(&saver, nr);
	nn = nil;
	mm = nil;
	ucount = 0;
	for(ll=nl; ll; ll=ll->next) {
		if(r == T)
			break;
		l = ll->n;
		if(isblank(l)) {
			r = structnext(&saver);
			continue;
		}

		// any lv that causes a fn call must be
		// deferred until all the return arguments
		// have been pulled from the output arguments
		if(fncall(l, r->type)) {
			tmp = temp(r->type);
			typecheck(&tmp, Erv);
			a = nod(OAS, l, tmp);
			a = convas(a, init);
			mm = list(mm, a);
			l = tmp;
		}

		a = nod(OAS, l, nodarg(r, fp));
		a = convas(a, init);
		ullmancalc(a);
		if(a->ullman >= UINF)
			ucount++;
		nn = list(nn, a);
		r = structnext(&saver);
	}

	if(ll != nil || r != T)
		yyerror("ascompatet: assignment count mismatch: %d = %d",
			count(nl), structcount(*nr));

	if(ucount)
		fatal("ascompatet: too many function calls evaluating parameters");
	return concat(nn, mm);
}

 /*
 * package all the arguments that match a ... T parameter into a []T.
 */
static NodeList*
mkdotargslice(NodeList *lr0, NodeList *nn, Type *l, int fp, NodeList **init, int esc)
{
	Node *a, *n;
	Type *tslice;

	tslice = typ(TARRAY);
	tslice->type = l->type->type;
	tslice->bound = -1;

	if(count(lr0) == 0) {
		n = nodnil();
		n->type = tslice;
	} else {
		n = nod(OCOMPLIT, N, typenod(tslice));
		n->list = lr0;
		n->esc = esc;
		typecheck(&n, Erv);
		if(n->type == T)
			fatal("mkdotargslice: typecheck failed");
		walkexpr(&n, init);
	}

	a = nod(OAS, nodarg(l, fp), n);
	nn = list(nn, convas(a, init));
	return nn;
}

/*
 * helpers for shape errors
 */
static char*
dumptypes(Type **nl, char *what)
{
	int first;
	Type *l;
	Iter savel;
	Fmt fmt;

	fmtstrinit(&fmt);
	fmtprint(&fmt, "\t");
	first = 1;
	for(l = structfirst(&savel, nl); l != T; l = structnext(&savel)) {
		if(first)
			first = 0;
		else
			fmtprint(&fmt, ", ");
		fmtprint(&fmt, "%T", l);
	}
	if(first)
		fmtprint(&fmt, "[no arguments %s]", what);
	return fmtstrflush(&fmt);
}

static char*
dumpnodetypes(NodeList *l, char *what)
{
	int first;
	Node *r;
	Fmt fmt;

	fmtstrinit(&fmt);
	fmtprint(&fmt, "\t");
	first = 1;
	for(; l; l=l->next) {
		r = l->n;
		if(first)
			first = 0;
		else
			fmtprint(&fmt, ", ");
		fmtprint(&fmt, "%T", r->type);
	}
	if(first)
		fmtprint(&fmt, "[no arguments %s]", what);
	return fmtstrflush(&fmt);
}

/*
 * check assign expression list to
 * a type list. called in
 *	return expr-list
 *	func(expr-list)
 */
static NodeList*
ascompatte(int op, Node *call, int isddd, Type **nl, NodeList *lr, int fp, NodeList **init)
{
	int esc;
	Type *l, *ll;
	Node *r, *a;
	NodeList *nn, *lr0, *alist;
	Iter savel;
	char *l1, *l2;

	lr0 = lr;
	l = structfirst(&savel, nl);
	r = N;
	if(lr)
		r = lr->n;
	nn = nil;

	// f(g()) where g has multiple return values
	if(r != N && lr->next == nil && r->type->etype == TSTRUCT && r->type->funarg) {
		// optimization - can do block copy
		if(eqtypenoname(r->type, *nl)) {
			a = nodarg(*nl, fp);
			a->type = r->type;
			nn = list1(convas(nod(OAS, a, r), init));
			goto ret;
		}

		// conversions involved.
		// copy into temporaries.
		alist = nil;
		for(l=structfirst(&savel, &r->type); l; l=structnext(&savel)) {
			a = temp(l->type);
			alist = list(alist, a);
		}
		a = nod(OAS2, N, N);
		a->list = alist;
		a->rlist = lr;
		typecheck(&a, Etop);
		walkstmt(&a);
		*init = list(*init, a);
		lr = alist;
		r = lr->n;
		l = structfirst(&savel, nl);
	}

loop:
	if(l != T && l->isddd) {
		// the ddd parameter must be last
		ll = structnext(&savel);
		if(ll != T)
			yyerror("... must be last argument");

		// special case --
		// only if we are assigning a single ddd
		// argument to a ddd parameter then it is
		// passed thru unencapsulated
		if(r != N && lr->next == nil && isddd && eqtype(l->type, r->type)) {
			a = nod(OAS, nodarg(l, fp), r);
			a = convas(a, init);
			nn = list(nn, a);
			goto ret;
		}

		// normal case -- make a slice of all
		// remaining arguments and pass it to
		// the ddd parameter.
		esc = EscUnknown;
		if(call->right)
			esc = call->right->esc;
		nn = mkdotargslice(lr, nn, l, fp, init, esc);
		goto ret;
	}

	if(l == T || r == N) {
		if(l != T || r != N) {
			l1 = dumptypes(nl, "expected");
			l2 = dumpnodetypes(lr0, "given");
			if(l != T)
				yyerror("not enough arguments to %O\n%s\n%s", op, l1, l2);
			else
				yyerror("too many arguments to %O\n%s\n%s", op, l1, l2);
		}
		goto ret;
	}

	a = nod(OAS, nodarg(l, fp), r);
	a = convas(a, init);
	nn = list(nn, a);

	l = structnext(&savel);
	r = N;
	lr = lr->next;
	if(lr != nil)
		r = lr->n;
	goto loop;

ret:
	for(lr=nn; lr; lr=lr->next)
		lr->n->typecheck = 1;
	return nn;
}

// generate code for print
static Node*
walkprint(Node *nn, NodeList **init, int defer)
{
	Node *r;
	Node *n;
	NodeList *l, *all;
	Node *on;
	Type *t;
	int notfirst, et, op;
	NodeList *calls, *intypes, *args;
	Fmt fmt;

	on = nil;
	op = nn->op;
	all = nn->list;
	calls = nil;
	notfirst = 0;
	intypes = nil;
	args = nil;

	memset(&fmt, 0, sizeof fmt);
	if(defer) {
		// defer print turns into defer printf with format string
		fmtstrinit(&fmt);
		intypes = list(intypes, nod(ODCLFIELD, N, typenod(types[TSTRING])));
		args = list1(nod(OXXX, N, N));
	}

	for(l=all; l; l=l->next) {
		if(notfirst) {
			if(defer)
				fmtprint(&fmt, " ");
			else
				calls = list(calls, mkcall("printsp", T, init));
		}
		notfirst = op == OPRINTN;

		n = l->n;
		if(n->op == OLITERAL) {
			switch(n->val.ctype) {
			case CTRUNE:
				defaultlit(&n, runetype);
				break;
			case CTINT:
				defaultlit(&n, types[TINT64]);
				break;
			case CTFLT:
				defaultlit(&n, types[TFLOAT64]);
				break;
			}
		}
		if(n->op != OLITERAL && n->type && n->type->etype == TIDEAL)
			defaultlit(&n, types[TINT64]);
		defaultlit(&n, nil);
		l->n = n;
		if(n->type == T || n->type->etype == TFORW)
			continue;

		t = n->type;
		et = n->type->etype;
		if(isinter(n->type)) {
			if(defer) {
				if(isnilinter(n->type))
					fmtprint(&fmt, "%%e");
				else
					fmtprint(&fmt, "%%i");
			} else {
				if(isnilinter(n->type))
					on = syslook("printeface", 1);
				else
					on = syslook("printiface", 1);
				argtype(on, n->type);		// any-1
			}
		} else if(isptr[et] || et == TCHAN || et == TMAP || et == TFUNC || et == TUNSAFEPTR) {
			if(defer) {
				fmtprint(&fmt, "%%p");
			} else {
				on = syslook("printpointer", 1);
				argtype(on, n->type);	// any-1
			}
		} else if(isslice(n->type)) {
			if(defer) {
				fmtprint(&fmt, "%%a");
			} else {
				on = syslook("printslice", 1);
				argtype(on, n->type);	// any-1
			}
		} else if(isint[et]) {
			if(defer) {
				if(et == TUINT64)
					fmtprint(&fmt, "%%U");
				else {
					fmtprint(&fmt, "%%D");
					t = types[TINT64];
				}
			} else {
				if(et == TUINT64)
					on = syslook("printuint", 0);
				else
					on = syslook("printint", 0);
			}
		} else if(isfloat[et]) {
			if(defer) {
				fmtprint(&fmt, "%%f");
				t = types[TFLOAT64];
			} else
				on = syslook("printfloat", 0);
		} else if(iscomplex[et]) {
			if(defer) {
				fmtprint(&fmt, "%%C");
				t = types[TCOMPLEX128];
			} else
				on = syslook("printcomplex", 0);
		} else if(et == TBOOL) {
			if(defer)
				fmtprint(&fmt, "%%t");
			else
				on = syslook("printbool", 0);
		} else if(et == TSTRING) {
			if(defer)
				fmtprint(&fmt, "%%S");
			else
				on = syslook("printstring", 0);
		} else {
			badtype(OPRINT, n->type, T);
			continue;
		}

		if(!defer) {
			t = *getinarg(on->type);
			if(t != nil)
				t = t->type;
			if(t != nil)
				t = t->type;
		}

		if(!eqtype(t, n->type)) {
			n = nod(OCONV, n, N);
			n->type = t;
		}

		if(defer) {
			intypes = list(intypes, nod(ODCLFIELD, N, typenod(t)));
			args = list(args, n);
		} else {
			r = nod(OCALL, on, N);
			r->list = list1(n);
			calls = list(calls, r);
		}
	}

	if(defer) {
		if(op == OPRINTN)
			fmtprint(&fmt, "\n");
		on = syslook("goprintf", 1);
		on->type = functype(nil, intypes, nil);
		args->n = nod(OLITERAL, N, N);
		args->n->val.ctype = CTSTR;
		args->n->val.u.sval = strlit(fmtstrflush(&fmt));
		r = nod(OCALL, on, N);
		r->list = args;
		typecheck(&r, Etop);
		walkexpr(&r, init);
	} else {
		if(op == OPRINTN)
			calls = list(calls, mkcall("printnl", T, nil));
		typechecklist(calls, Etop);
		walkexprlist(calls, init);

		r = nod(OEMPTY, N, N);
		typecheck(&r, Etop);
		walkexpr(&r, init);
		r->ninit = calls;
	}
	return r;
}

Node*
callnew(Type *t)
{
	Node *fn;

	dowidth(t);
	fn = syslook("new", 1);
	argtype(fn, t);
	return mkcall1(fn, ptrto(t), nil, typename(t));
}

static Node*
convas(Node *n, NodeList **init)
{
	Type *lt, *rt;

	if(n->op != OAS)
		fatal("convas: not OAS %O", n->op);

	n->typecheck = 1;

	if(n->left == N || n->right == N)
		goto out;

	lt = n->left->type;
	rt = n->right->type;
	if(lt == T || rt == T)
		goto out;

	if(isblank(n->left)) {
		defaultlit(&n->right, T);
		goto out;
	}

	if(n->left->op == OINDEXMAP) {
		n = mkcall1(mapfn("mapassign1", n->left->left->type), T, init,
			typename(n->left->left->type),
			n->left->left, n->left->right, n->right);
		goto out;
	}

	if(eqtype(lt, rt))
		goto out;

	n->right = assignconv(n->right, lt, "assignment");
	walkexpr(&n->right, init);

out:
	ullmancalc(n);
	return n;
}

/*
 * from ascompat[te]
 * evaluating actual function arguments.
 *	f(a,b)
 * if there is exactly one function expr,
 * then it is done first. otherwise must
 * make temp variables
 */
static NodeList*
reorder1(NodeList *all)
{
	Node *f, *a, *n;
	NodeList *l, *r, *g;
	int c, d, t;

	c = 0;	// function calls
	t = 0;	// total parameters

	for(l=all; l; l=l->next) {
		n = l->n;
		t++;
		ullmancalc(n);
		if(n->ullman >= UINF)
			c++;
	}
	if(c == 0 || t == 1)
		return all;

	g = nil;	// fncalls assigned to tempnames
	f = N;	// last fncall assigned to stack
	r = nil;	// non fncalls and tempnames assigned to stack
	d = 0;
	for(l=all; l; l=l->next) {
		n = l->n;
		if(n->ullman < UINF) {
			r = list(r, n);
			continue;
		}
		d++;
		if(d == c) {
			f = n;
			continue;
		}

		// make assignment of fncall to tempname
		a = temp(n->right->type);
		a = nod(OAS, a, n->right);
		g = list(g, a);

		// put normal arg assignment on list
		// with fncall replaced by tempname
		n->right = a->left;
		r = list(r, n);
	}

	if(f != N)
		g = list(g, f);
	return concat(g, r);
}

static void reorder3save(Node**, NodeList*, NodeList*, NodeList**);
static int aliased(Node*, NodeList*, NodeList*);

/*
 * from ascompat[ee]
 *	a,b = c,d
 * simultaneous assignment. there cannot
 * be later use of an earlier lvalue.
 *
 * function calls have been removed.
 */
static NodeList*
reorder3(NodeList *all)
{
	NodeList *list, *early;
	Node *l;

	// If a needed expression may be affected by an
	// earlier assignment, make an early copy of that
	// expression and use the copy instead.
	early = nil;
	for(list=all; list; list=list->next) {
		l = list->n->left;

		// Save subexpressions needed on left side.
		// Drill through non-dereferences.
		for(;;) {
			if(l->op == ODOT || l->op == OPAREN) {
				l = l->left;
				continue;
			}
			if(l->op == OINDEX && isfixedarray(l->left->type)) {
				reorder3save(&l->right, all, list, &early);
				l = l->left;
				continue;
			}
			break;
		}
		switch(l->op) {
		default:
			fatal("reorder3 unexpected lvalue %#O", l->op);
		case ONAME:
			break;
		case OINDEX:
			reorder3save(&l->left, all, list, &early);
			reorder3save(&l->right, all, list, &early);
			break;
		case OIND:
		case ODOTPTR:
			reorder3save(&l->left, all, list, &early);
		}

		// Save expression on right side.
		reorder3save(&list->n->right, all, list, &early);
	}

	return concat(early, all);
}

static int vmatch2(Node*, Node*);
static int varexpr(Node*);

/*
 * if the evaluation of *np would be affected by the 
 * assignments in all up to but not including stop,
 * copy into a temporary during *early and
 * replace *np with that temp.
 */
static void
reorder3save(Node **np, NodeList *all, NodeList *stop, NodeList **early)
{
	Node *n, *q;

	n = *np;
	if(!aliased(n, all, stop))
		return;
	
	q = temp(n->type);
	q = nod(OAS, q, n);
	typecheck(&q, Etop);
	*early = list(*early, q);
	*np = q->left;
}

/*
 * what's the outer value that a write to n affects?
 * outer value means containing struct or array.
 */
static Node*
outervalue(Node *n)
{	
	for(;;) {
		if(n->op == ODOT || n->op == OPAREN) {
			n = n->left;
			continue;
		}
		if(n->op == OINDEX && isfixedarray(n->left->type)) {
			n = n->left;
			continue;
		}
		break;
	}
	return n;
}

/*
 * Is it possible that the computation of n might be
 * affected by writes in as up to but not including stop?
 */
static int
aliased(Node *n, NodeList *all, NodeList *stop)
{
	int memwrite, varwrite;
	Node *a;
	NodeList *l;

	if(n == N)
		return 0;

	// Look for obvious aliasing: a variable being assigned
	// during the all list and appearing in n.
	// Also record whether there are any writes to main memory.
	// Also record whether there are any writes to variables
	// whose addresses have been taken.
	memwrite = 0;
	varwrite = 0;
	for(l=all; l!=stop; l=l->next) {
		a = outervalue(l->n->left);
		if(a->op != ONAME) {
			memwrite = 1;
			continue;
		}
		switch(n->class) {
		default:
			varwrite = 1;
			continue;
		case PAUTO:
		case PPARAM:
		case PPARAMOUT:
			if(n->addrtaken) {
				varwrite = 1;
				continue;
			}
			if(vmatch2(a, n)) {
				// Direct hit.
				return 1;
			}
		}
	}

	// The variables being written do not appear in n.
	// However, n might refer to computed addresses
	// that are being written.
	
	// If no computed addresses are affected by the writes, no aliasing.
	if(!memwrite && !varwrite)
		return 0;

	// If n does not refer to computed addresses
	// (that is, if n only refers to variables whose addresses
	// have not been taken), no aliasing.
	if(varexpr(n))
		return 0;

	// Otherwise, both the writes and n refer to computed memory addresses.
	// Assume that they might conflict.
	return 1;
}

/*
 * does the evaluation of n only refer to variables
 * whose addresses have not been taken?
 * (and no other memory)
 */
static int
varexpr(Node *n)
{
	if(n == N)
		return 1;

	switch(n->op) {
	case OLITERAL:	
		return 1;
	case ONAME:
		switch(n->class) {
		case PAUTO:
		case PPARAM:
		case PPARAMOUT:
			if(!n->addrtaken)
				return 1;
		}
		return 0;

	case OADD:
	case OSUB:
	case OOR:
	case OXOR:
	case OMUL:
	case ODIV:
	case OMOD:
	case OLSH:
	case ORSH:
	case OAND:
	case OANDNOT:
	case OPLUS:
	case OMINUS:
	case OCOM:
	case OPAREN:
	case OANDAND:
	case OOROR:
	case ODOT:  // but not ODOTPTR
	case OCONV:
	case OCONVNOP:
	case OCONVIFACE:
	case ODOTTYPE:
		return varexpr(n->left) && varexpr(n->right);
	}

	// Be conservative.
	return 0;
}

/*
 * is the name l mentioned in r?
 */
static int
vmatch2(Node *l, Node *r)
{
	NodeList *ll;

	if(r == N)
		return 0;
	switch(r->op) {
	case ONAME:
		// match each right given left
		return l == r;
	case OLITERAL:
		return 0;
	}
	if(vmatch2(l, r->left))
		return 1;
	if(vmatch2(l, r->right))
		return 1;
	for(ll=r->list; ll; ll=ll->next)
		if(vmatch2(l, ll->n))
			return 1;
	return 0;
}

/*
 * is any name mentioned in l also mentioned in r?
 * called by sinit.c
 */
int
vmatch1(Node *l, Node *r)
{
	NodeList *ll;

	/*
	 * isolate all left sides
	 */
	if(l == N || r == N)
		return 0;
	switch(l->op) {
	case ONAME:
		switch(l->class) {
		case PPARAM:
		case PPARAMREF:
		case PAUTO:
			break;
		default:
			// assignment to non-stack variable
			// must be delayed if right has function calls.
			if(r->ullman >= UINF)
				return 1;
			break;
		}
		return vmatch2(l, r);
	case OLITERAL:
		return 0;
	}
	if(vmatch1(l->left, r))
		return 1;
	if(vmatch1(l->right, r))
		return 1;
	for(ll=l->list; ll; ll=ll->next)
		if(vmatch1(ll->n, r))
			return 1;
	return 0;
}

/*
 * walk through argin parameters.
 * generate and return code to allocate
 * copies of escaped parameters to the heap.
 */
static NodeList*
paramstoheap(Type **argin, int out)
{
	Type *t;
	Iter savet;
	Node *v;
	NodeList *nn;

	nn = nil;
	for(t = structfirst(&savet, argin); t != T; t = structnext(&savet)) {
		v = t->nname;
		if(v == N && out && hasdefer) {
			// Defer might stop a panic and show the
			// return values as they exist at the time of panic.
			// Make sure to zero them on entry to the function.
			nn = list(nn, nod(OAS, nodarg(t, 1), N));
		}
		if(v == N || !(v->class & PHEAP))
			continue;

		// generate allocation & copying code
		if(v->alloc == nil)
			v->alloc = callnew(v->type);
		nn = list(nn, nod(OAS, v->heapaddr, v->alloc));
		if((v->class & ~PHEAP) != PPARAMOUT)
			nn = list(nn, nod(OAS, v, v->stackparam));
	}
	return nn;
}

/*
 * walk through argout parameters copying back to stack
 */
static NodeList*
returnsfromheap(Type **argin)
{
	Type *t;
	Iter savet;
	Node *v;
	NodeList *nn;

	nn = nil;
	for(t = structfirst(&savet, argin); t != T; t = structnext(&savet)) {
		v = t->nname;
		if(v == N || v->class != (PHEAP|PPARAMOUT))
			continue;
		nn = list(nn, nod(OAS, v->stackparam, v));
	}
	return nn;
}

/*
 * take care of migrating any function in/out args
 * between the stack and the heap.  adds code to
 * curfn's before and after lists.
 */
static void
heapmoves(void)
{
	NodeList *nn;
	int32 lno;

	lno = lineno;
	lineno = curfn->lineno;
	nn = paramstoheap(getthis(curfn->type), 0);
	nn = concat(nn, paramstoheap(getinarg(curfn->type), 0));
	nn = concat(nn, paramstoheap(getoutarg(curfn->type), 1));
	curfn->enter = concat(curfn->enter, nn);
	lineno = curfn->endlineno;
	curfn->exit = returnsfromheap(getoutarg(curfn->type));
	lineno = lno;
}

static Node*
vmkcall(Node *fn, Type *t, NodeList **init, va_list va)
{
	int i, n;
	Node *r;
	NodeList *args;

	if(fn->type == T || fn->type->etype != TFUNC)
		fatal("mkcall %N %T", fn, fn->type);

	args = nil;
	n = fn->type->intuple;
	for(i=0; i<n; i++)
		args = list(args, va_arg(va, Node*));

	r = nod(OCALL, fn, N);
	r->list = args;
	if(fn->type->outtuple > 0)
		typecheck(&r, Erv | Efnstruct);
	else
		typecheck(&r, Etop);
	walkexpr(&r, init);
	r->type = t;
	return r;
}

Node*
mkcall(char *name, Type *t, NodeList **init, ...)
{
	Node *r;
	va_list va;

	va_start(va, init);
	r = vmkcall(syslook(name, 0), t, init, va);
	va_end(va);
	return r;
}

Node*
mkcall1(Node *fn, Type *t, NodeList **init, ...)
{
	Node *r;
	va_list va;

	va_start(va, init);
	r = vmkcall(fn, t, init, va);
	va_end(va);
	return r;
}

Node*
conv(Node *n, Type *t)
{
	if(eqtype(n->type, t))
		return n;
	n = nod(OCONV, n, N);
	n->type = t;
	typecheck(&n, Erv);
	return n;
}

Node*
chanfn(char *name, int n, Type *t)
{
	Node *fn;
	int i;

	if(t->etype != TCHAN)
		fatal("chanfn %T", t);
	fn = syslook(name, 1);
	for(i=0; i<n; i++)
		argtype(fn, t->type);
	return fn;
}

static Node*
mapfn(char *name, Type *t)
{
	Node *fn;

	if(t->etype != TMAP)
		fatal("mapfn %T", t);
	fn = syslook(name, 1);
	argtype(fn, t->down);
	argtype(fn, t->type);
	argtype(fn, t->down);
	argtype(fn, t->type);
	return fn;
}

static Node*
mapfndel(char *name, Type *t)
{
	Node *fn;

	if(t->etype != TMAP)
		fatal("mapfn %T", t);
	fn = syslook(name, 1);
	argtype(fn, t->down);
	argtype(fn, t->type);
	argtype(fn, t->down);
	return fn;
}

static Node*
addstr(Node *n, NodeList **init)
{
	Node *r, *cat, *typstr;
	NodeList *in, *args;
	int i, count;

	count = 0;
	for(r=n; r->op == OADDSTR; r=r->left)
		count++;	// r->right
	count++;	// r

	// prepare call of runtime.catstring of type int, string, string, string
	// with as many strings as we have.
	cat = syslook("concatstring", 1);
	cat->type = T;
	cat->ntype = nod(OTFUNC, N, N);
	in = list1(nod(ODCLFIELD, N, typenod(types[TINT])));	// count
	typstr = typenod(types[TSTRING]);
	for(i=0; i<count; i++)
		in = list(in, nod(ODCLFIELD, N, typstr));
	cat->ntype->list = in;
	cat->ntype->rlist = list1(nod(ODCLFIELD, N, typstr));

	args = nil;
	for(r=n; r->op == OADDSTR; r=r->left)
		args = concat(list1(conv(r->right, types[TSTRING])), args);
	args = concat(list1(conv(r, types[TSTRING])), args);
	args = concat(list1(nodintconst(count)), args);

	r = nod(OCALL, cat, N);
	r->list = args;
	typecheck(&r, Erv);
	walkexpr(&r, init);
	r->type = n->type;

	return r;
}

static Node*
appendslice(Node *n, NodeList **init)
{
	Node *f;

	f = syslook("appendslice", 1);
	argtype(f, n->type);
	argtype(f, n->type->type);
	argtype(f, n->type);
	return mkcall1(f, n->type, init, typename(n->type), n->list->n, n->list->next->n);
}

// expand append(src, a [, b]* ) to
//
//   init {
//     s := src
//     const argc = len(args) - 1
//     if cap(s) - len(s) < argc {
//	    s = growslice(s, argc)
//     }
//     n := len(s)
//     s = s[:n+argc]
//     s[n] = a
//     s[n+1] = b
//     ...
//   }
//   s
static Node*
append(Node *n, NodeList **init)
{
	NodeList *l, *a;
	Node *nsrc, *ns, *nn, *na, *nx, *fn;
	int argc;

	walkexprlistsafe(n->list, init);

	// walkexprlistsafe will leave OINDEX (s[n]) alone if both s
	// and n are name or literal, but those may index the slice we're
	// modifying here.  Fix explicitly.
	for(l=n->list; l; l=l->next)
		l->n = cheapexpr(l->n, init);

	nsrc = n->list->n;
	argc = count(n->list) - 1;
	if (argc < 1) {
		return nsrc;
	}

	l = nil;

	ns = temp(nsrc->type);
	l = list(l, nod(OAS, ns, nsrc));  // s = src

	na = nodintconst(argc);		// const argc
	nx = nod(OIF, N, N);		// if cap(s) - len(s) < argc
	nx->ntest = nod(OLT, nod(OSUB, nod(OCAP, ns, N), nod(OLEN, ns, N)), na);

	fn = syslook("growslice", 1);	//   growslice(<type>, old []T, n int64) (ret []T)
	argtype(fn, ns->type->type);	// 1 old []any
	argtype(fn, ns->type->type);	// 2 ret []any

	nx->nbody = list1(nod(OAS, ns, mkcall1(fn,  ns->type, &nx->ninit,
					       typename(ns->type),
					       ns,
					       conv(na, types[TINT64]))));
	l = list(l, nx);

	nn = temp(types[TINT]);
	l = list(l, nod(OAS, nn, nod(OLEN, ns, N)));	 // n = len(s)

	nx = nod(OSLICE, ns, nod(OKEY, N, nod(OADD, nn, na)));	 // ...s[:n+argc]
	nx->etype = 1;	// disable bounds check
	l = list(l, nod(OAS, ns, nx));			// s = s[:n+argc]

	for (a = n->list->next;	 a != nil; a = a->next) {
		nx = nod(OINDEX, ns, nn);		// s[n] ...
		nx->etype = 1;	// disable bounds check
		l = list(l, nod(OAS, nx, a->n));	// s[n] = arg
		if (a->next != nil)
			l = list(l, nod(OAS, nn, nod(OADD, nn, nodintconst(1))));  // n = n + 1
	}

	typechecklist(l, Etop);
	walkstmtlist(l);
	*init = concat(*init, l);
	return ns;
}

static Node*
eqfor(Type *t)
{
	int a;
	Node *n;
	Node *ntype;
	Sym *sym;

	// Should only arrive here with large memory or
	// a struct/array containing a non-memory field/element.
	// Small memory is handled inline, and single non-memory
	// is handled during type check (OCMPSTR etc).
	a = algtype1(t, nil);
	if(a != AMEM && a != -1)
		fatal("eqfor %T", t);

	if(a == AMEM) {
		n = syslook("memequal", 1);
		argtype(n, t);
		argtype(n, t);
		return n;
	}

	sym = typesymprefix(".eq", t);
	n = newname(sym);
	n->class = PFUNC;
	ntype = nod(OTFUNC, N, N);
	ntype->list = list(ntype->list, nod(ODCLFIELD, N, typenod(ptrto(types[TBOOL]))));
	ntype->list = list(ntype->list, nod(ODCLFIELD, N, typenod(types[TUINTPTR])));
	ntype->list = list(ntype->list, nod(ODCLFIELD, N, typenod(ptrto(t))));
	ntype->list = list(ntype->list, nod(ODCLFIELD, N, typenod(ptrto(t))));
	typecheck(&ntype, Etype);
	n->type = ntype->type;
	return n;
}

static int
countfield(Type *t)
{
	Type *t1;
	int n;
	
	n = 0;
	for(t1=t->type; t1!=T; t1=t1->down)
		n++;
	return n;
}

static void
walkcompare(Node **np, NodeList **init)
{
	Node *n, *l, *r, *fn, *call, *a, *li, *ri, *expr;
	int andor, i;
	Type *t, *t1;
	static Node *tempbool;
	
	n = *np;
	
	// Must be comparison of array or struct.
	// Otherwise back end handles it.
	t = n->left->type;
	switch(t->etype) {
	default:
		return;
	case TARRAY:
		if(isslice(t))
			return;
		break;
	case TSTRUCT:
		break;
	}
	
	if(!islvalue(n->left) || !islvalue(n->right))
		goto hard;

	l = temp(ptrto(t));
	a = nod(OAS, l, nod(OADDR, n->left, N));
	a->right->etype = 1;  // addr does not escape
	typecheck(&a, Etop);
	*init = list(*init, a);

	r = temp(ptrto(t));
	a = nod(OAS, r, nod(OADDR, n->right, N));
	a->right->etype = 1;  // addr does not escape
	typecheck(&a, Etop);
	*init = list(*init, a);

	expr = N;
	andor = OANDAND;
	if(n->op == ONE)
		andor = OOROR;

	if(t->etype == TARRAY &&
		t->bound <= 4 &&
		issimple[t->type->etype]) {
		// Four or fewer elements of a basic type.
		// Unroll comparisons.
		for(i=0; i<t->bound; i++) {
			li = nod(OINDEX, l, nodintconst(i));
			ri = nod(OINDEX, r, nodintconst(i));
			a = nod(n->op, li, ri);
			if(expr == N)
				expr = a;
			else
				expr = nod(andor, expr, a);
		}
		if(expr == N)
			expr = nodbool(n->op == OEQ);
		typecheck(&expr, Erv);
		walkexpr(&expr, init);
		expr->type = n->type;
		*np = expr;
		return;
	}
	
	if(t->etype == TSTRUCT && countfield(t) <= 4) {
		// Struct of four or fewer fields.
		// Inline comparisons.
		for(t1=t->type; t1; t1=t1->down) {
			li = nod(OXDOT, l, newname(t1->sym));
			ri = nod(OXDOT, r, newname(t1->sym));
			a = nod(n->op, li, ri);
			if(expr == N)
				expr = a;
			else
				expr = nod(andor, expr, a);
		}
		if(expr == N)
			expr = nodbool(n->op == OEQ);
		typecheck(&expr, Erv);
		walkexpr(&expr, init);
		expr->type = n->type;
		*np = expr;
		return;
	}
	
	// Chose not to inline, but still have addresses.
	// Call equality function directly.
	// The equality function requires a bool pointer for
	// storing its address, because it has to be callable
	// from C, and C can't access an ordinary Go return value.
	// To avoid creating many temporaries, cache one per function.
	if(tempbool == N || tempbool->curfn != curfn)
		tempbool = temp(types[TBOOL]);
	
	call = nod(OCALL, eqfor(t), N);
	a = nod(OADDR, tempbool, N);
	a->etype = 1;  // does not escape
	call->list = list(call->list, a);
	call->list = list(call->list, nodintconst(t->width));
	call->list = list(call->list, l);
	call->list = list(call->list, r);
	typecheck(&call, Etop);
	walkstmt(&call);
	*init = list(*init, call);
	
	if(n->op == OEQ)
		r = tempbool;
	else
		r = nod(ONOT, tempbool, N);
	typecheck(&r, Erv);
	walkexpr(&r, init);
	*np = r;
	return;

hard:
	// Cannot take address of one or both of the operands.
	// Instead, pass directly to runtime helper function.
	// Easier on the stack than passing the address
	// of temporary variables, because we are better at reusing
	// the argument space than temporary variable space.
	fn = syslook("equal", 1);
	l = n->left;
	r = n->right;
	argtype(fn, n->left->type);
	argtype(fn, n->left->type);
	r = mkcall1(fn, n->type, init, typename(n->left->type), l, r);
	if(n->op == ONE) {
		r = nod(ONOT, r, N);
		typecheck(&r, Erv);
	}
	*np = r;
	return;
}
