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

// Escape analysis.

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

// Run analysis on minimal sets of mutually recursive functions
// or single non-recursive functions, bottom up.
//
// Finding these sets is finding strongly connected components
// in the static call graph.  The algorithm for doing that is taken
// from Sedgewick, Algorithms, Second Edition, p. 482, with two
// adaptations.
//
// First, a hidden closure function (n->curfn != N) cannot be the
// root of a connected component. Refusing to use it as a root
// forces it into the component of the function in which it appears.
// The analysis assumes that closures and the functions in which they
// appear are analyzed together, so that the aliasing between their
// variables can be modeled more precisely.
//
// Second, each function becomes two virtual nodes in the graph,
// with numbers n and n+1. We record the function's node number as n
// but search from node n+1. If the search tells us that the component
// number (min) is n+1, we know that this is a trivial component: one function
// plus its closures. If the search tells us that the component number is
// n, then there was a path from node n+1 back to node n, meaning that
// the function set is mutually recursive. The escape analysis can be
// more precise when analyzing a single non-recursive function than
// when analyzing a set of mutually recursive functions.

static NodeList *stack;
static uint32 visitgen;
static uint32 visit(Node*);
static uint32 visitcode(Node*, uint32);
static uint32 visitcodelist(NodeList*, uint32);

static void analyze(NodeList*, int);

enum
{
	EscFuncUnknown = 0,
	EscFuncPlanned,
	EscFuncStarted,
	EscFuncTagged,
};

void
escapes(NodeList *all)
{
	NodeList *l;

	for(l=all; l; l=l->next)
		l->n->walkgen = 0;

	visitgen = 0;
	for(l=all; l; l=l->next)
		if(l->n->op == ODCLFUNC && l->n->curfn == N)
			visit(l->n);

	for(l=all; l; l=l->next)
		l->n->walkgen = 0;
}

static uint32
visit(Node *n)
{
	uint32 min, recursive;
	NodeList *l, *block;

	if(n->walkgen > 0) {
		// already visited
		return n->walkgen;
	}
	
	visitgen++;
	n->walkgen = visitgen;
	visitgen++;
	min = visitgen;

	l = mal(sizeof *l);
	l->next = stack;
	l->n = n;
	stack = l;
	min = visitcodelist(n->nbody, min);
	if((min == n->walkgen || min == n->walkgen+1) && n->curfn == N) {
		// This node is the root of a strongly connected component.

		// The original min passed to visitcodelist was n->walkgen+1.
		// If visitcodelist found its way back to n->walkgen, then this
		// block is a set of mutually recursive functions.
		// Otherwise it's just a lone function that does not recurse.
		recursive = min == n->walkgen;

		// Remove connected component from stack.
		// Mark walkgen so that future visits return a large number
		// so as not to affect the caller's min.
		block = stack;
		for(l=stack; l->n != n; l=l->next)
			l->n->walkgen = (uint32)~0U;
		n->walkgen = (uint32)~0U;
		stack = l->next;
		l->next = nil;

		// Run escape analysis on this set of functions.
		analyze(block, recursive);
	}

	return min;
}

static uint32
visitcodelist(NodeList *l, uint32 min)
{
	for(; l; l=l->next)
		min = visitcode(l->n, min);
	return min;
}

static uint32
visitcode(Node *n, uint32 min)
{
	Node *fn;
	uint32 m;

	if(n == N)
		return min;

	min = visitcodelist(n->ninit, min);
	min = visitcode(n->left, min);
	min = visitcode(n->right, min);
	min = visitcodelist(n->list, min);
	min = visitcode(n->ntest, min);
	min = visitcode(n->nincr, min);
	min = visitcodelist(n->nbody, min);
	min = visitcodelist(n->nelse, min);
	min = visitcodelist(n->rlist, min);
	
	if(n->op == OCALLFUNC || n->op == OCALLMETH) {
		fn = n->left;
		if(n->op == OCALLMETH)
			fn = n->left->right->sym->def;
		if(fn && fn->op == ONAME && fn->class == PFUNC && fn->defn && fn->defn->nbody)
			if((m = visit(fn->defn)) < min)
				min = m;
	}
	
	if(n->op == OCLOSURE)
		if((m = visit(n->closure)) < min)
			min = m;

	return min;
}

// An escape analysis pass for a set of functions.
//
// First escfunc, esc and escassign recurse over the ast of each
// function to dig out flow(dst,src) edges between any
// pointer-containing nodes and store them in dst->escflowsrc.  For
// variables assigned to a variable in an outer scope or used as a
// return value, they store a flow(theSink, src) edge to a fake node
// 'the Sink'.  For variables referenced in closures, an edge
// flow(closure, &var) is recorded and the flow of a closure itself to
// an outer scope is tracked the same way as other variables.
//
// Then escflood walks the graph starting at theSink and tags all
// variables of it can reach an & node as escaping and all function
// parameters it can reach as leaking.
//
// If a value's address is taken but the address does not escape,
// then the value can stay on the stack.  If the value new(T) does
// not escape, then new(T) can be rewritten into a stack allocation.
// The same is true of slice literals.
//
// If optimizations are disabled (-N), this code is not used.
// Instead, the compiler assumes that any value whose address
// is taken without being immediately dereferenced
// needs to be moved to the heap, and new(T) and slice
// literals are always real allocations.

typedef struct EscState EscState;

static void escfunc(EscState*, Node *func);
static void esclist(EscState*, NodeList *l);
static void esc(EscState*, Node *n);
static void escloopdepthlist(EscState*, NodeList *l);
static void escloopdepth(EscState*, Node *n);
static void escassign(EscState*, Node *dst, Node *src);
static void esccall(EscState*, Node*);
static void escflows(EscState*, Node *dst, Node *src);
static void escflood(EscState*, Node *dst);
static void escwalk(EscState*, int level, Node *dst, Node *src);
static void esctag(EscState*, Node *func);

struct EscState {
	// Fake node that all
	//   - return values and output variables
	//   - parameters on imported functions not marked 'safe'
	//   - assignments to global variables
	// flow to.
	Node	theSink;
	
	NodeList*	dsts;		// all dst nodes
	int	loopdepth;	// for detecting nested loop scopes
	int	pdepth;		// for debug printing in recursions.
	int	dstcount, edgecount;	// diagnostic
	NodeList*	noesc;	// list of possible non-escaping nodes, for printing
};

static Strlit*	safetag;	// gets slapped on safe parameters' field types for export

static void
analyze(NodeList *all, int recursive)
{
	NodeList *l;
	EscState es, *e;
	
	USED(recursive);

	memset(&es, 0, sizeof es);
	e = &es;
	e->theSink.op = ONAME;
	e->theSink.orig = &e->theSink;
	e->theSink.class = PEXTERN;
	e->theSink.sym = lookup(".sink");
	e->theSink.escloopdepth = -1;

	if(safetag == nil)
		safetag = strlit("noescape");

	for(l=all; l; l=l->next)
		if(l->n->op == ODCLFUNC)
			l->n->esc = EscFuncPlanned;

	// flow-analyze functions
	for(l=all; l; l=l->next)
		if(l->n->op == ODCLFUNC)
			escfunc(e, l->n);

	// print("escapes: %d e->dsts, %d edges\n", e->dstcount, e->edgecount);

	// visit the updstream of each dst, mark address nodes with
	// addrescapes, mark parameters unsafe
	for(l = e->dsts; l; l=l->next)
		escflood(e, l->n);

	// for all top level functions, tag the typenodes corresponding to the param nodes
	for(l=all; l; l=l->next)
		if(l->n->op == ODCLFUNC)
			esctag(e, l->n);

	if(debug['m']) {
		for(l=e->noesc; l; l=l->next)
			if(l->n->esc == EscNone)
				warnl(l->n->lineno, "%S %hN does not escape",
					(l->n->curfn && l->n->curfn->nname) ? l->n->curfn->nname->sym : S,
					l->n);
	}
}


static void
escfunc(EscState *e, Node *func)
{
	Node *savefn;
	NodeList *ll;
	int saveld;

	if(func->esc != 1)
		fatal("repeat escfunc %N", func->nname);
	func->esc = EscFuncStarted;

	saveld = e->loopdepth;
	e->loopdepth = 1;
	savefn = curfn;
	curfn = func;

	for(ll=curfn->dcl; ll; ll=ll->next) {
		if(ll->n->op != ONAME)
			continue;
		switch (ll->n->class) {
		case PPARAMOUT:
			// output parameters flow to the sink
			escflows(e, &e->theSink, ll->n);
			ll->n->escloopdepth = e->loopdepth;
			break;
		case PPARAM:
			if(ll->n->type && !haspointers(ll->n->type))
				break;
			ll->n->esc = EscNone;	// prime for escflood later
			e->noesc = list(e->noesc, ll->n);
			ll->n->escloopdepth = e->loopdepth;
			break;
		}
	}

	escloopdepthlist(e, curfn->nbody);
	esclist(e, curfn->nbody);
	curfn = savefn;
	e->loopdepth = saveld;
}

// Mark labels that have no backjumps to them as not increasing e->loopdepth.
// Walk hasn't generated (goto|label)->left->sym->label yet, so we'll cheat
// and set it to one of the following two.  Then in esc we'll clear it again.
static Label looping;
static Label nonlooping;

static void
escloopdepthlist(EscState *e, NodeList *l)
{
	for(; l; l=l->next)
		escloopdepth(e, l->n);
}

static void
escloopdepth(EscState *e, Node *n)
{
	if(n == N)
		return;

	escloopdepthlist(e, n->ninit);

	switch(n->op) {
	case OLABEL:
		if(!n->left || !n->left->sym)
			fatal("esc:label without label: %+N", n);
		// Walk will complain about this label being already defined, but that's not until
		// after escape analysis. in the future, maybe pull label & goto analysis out of walk and put before esc
		// if(n->left->sym->label != nil)
		//	fatal("escape analysis messed up analyzing label: %+N", n);
		n->left->sym->label = &nonlooping;
		break;
	case OGOTO:
		if(!n->left || !n->left->sym)
			fatal("esc:goto without label: %+N", n);
		// If we come past one that's uninitialized, this must be a (harmless) forward jump
		// but if it's set to nonlooping the label must have preceded this goto.
		if(n->left->sym->label == &nonlooping)
			n->left->sym->label = &looping;
		break;
	}

	escloopdepth(e, n->left);
	escloopdepth(e, n->right);
	escloopdepthlist(e, n->list);
	escloopdepth(e, n->ntest);
	escloopdepth(e, n->nincr);
	escloopdepthlist(e, n->nbody);
	escloopdepthlist(e, n->nelse);
	escloopdepthlist(e, n->rlist);

}

static void
esclist(EscState *e, NodeList *l)
{
	for(; l; l=l->next)
		esc(e, l->n);
}

static void
esc(EscState *e, Node *n)
{
	int lno;
	NodeList *ll, *lr;
	Node *a;

	if(n == N)
		return;

	lno = setlineno(n);

	if(n->op == OFOR || n->op == ORANGE)
		e->loopdepth++;

	esc(e, n->left);
	esc(e, n->right);
	esc(e, n->ntest);
	esc(e, n->nincr);
	esclist(e, n->ninit);
	esclist(e, n->nbody);
	esclist(e, n->nelse);
	esclist(e, n->list);
	esclist(e, n->rlist);

	if(n->op == OFOR || n->op == ORANGE)
		e->loopdepth--;

	if(debug['m'] > 1)
		print("%L:[%d] %S esc: %N\n", lineno, e->loopdepth,
		      (curfn && curfn->nname) ? curfn->nname->sym : S, n);

	switch(n->op) {
	case ODCL:
		// Record loop depth at declaration.
		if(n->left)
			n->left->escloopdepth = e->loopdepth;
		break;

	case OLABEL:
		if(n->left->sym->label == &nonlooping) {
			if(debug['m'] > 1)
				print("%L:%N non-looping label\n", lineno, n);
		} else if(n->left->sym->label == &looping) {
			if(debug['m'] > 1)
				print("%L: %N looping label\n", lineno, n);
			e->loopdepth++;
		}
		// See case OLABEL in escloopdepth above
		// else if(n->left->sym->label == nil)
		//	fatal("escape anaylysis missed or messed up a label: %+N", n);

		n->left->sym->label = nil;

	case ORANGE:
		// Everything but fixed array is a dereference.
		if(isfixedarray(n->type) && n->list->next)
			escassign(e, n->list->next->n, n->right);
		break;

	case OSWITCH:
		if(n->ntest && n->ntest->op == OTYPESW) {
			for(ll=n->list; ll; ll=ll->next) {  // cases
				// ntest->right is the argument of the .(type),
				// ll->n->nname is the variable per case
				escassign(e, ll->n->nname, n->ntest->right);
			}
		}
		break;

	case OAS:
	case OASOP:
		escassign(e, n->left, n->right);
		break;

	case OAS2:	// x,y = a,b
		if(count(n->list) == count(n->rlist))
			for(ll=n->list, lr=n->rlist; ll; ll=ll->next, lr=lr->next)
				escassign(e, ll->n, lr->n);
		break;

	case OAS2RECV:		// v, ok = <-ch
	case OAS2MAPR:		// v, ok = m[k]
	case OAS2DOTTYPE:	// v, ok = x.(type)
		escassign(e, n->list->n, n->rlist->n);
		break;

	case OSEND:		// ch <- x
		escassign(e, &e->theSink, n->right);
		break;

	case ODEFER:
		if(e->loopdepth == 1)  // top level
			break;
		// arguments leak out of scope
		// TODO: leak to a dummy node instead
		// fallthrough
	case OPROC:
		// go f(x) - f and x escape
		escassign(e, &e->theSink, n->left->left);
		escassign(e, &e->theSink, n->left->right);  // ODDDARG for call
		for(ll=n->left->list; ll; ll=ll->next)
			escassign(e, &e->theSink, ll->n);
		break;

	case ORETURN:
		for(ll=n->list; ll; ll=ll->next)
			escassign(e, &e->theSink, ll->n);
		break;

	case OPANIC:
		// Argument could leak through recover.
		escassign(e, &e->theSink, n->left);
		break;

	case OAPPEND:
		if(!n->isddd)
			for(ll=n->list->next; ll; ll=ll->next)
				escassign(e, &e->theSink, ll->n);  // lose track of assign to dereference
		break;

	case OCALLMETH:
	case OCALLFUNC:
	case OCALLINTER:
		esccall(e, n);
		break;

	case OCONV:
	case OCONVNOP:
	case OCONVIFACE:
		escassign(e, n, n->left);
		break;

	case OARRAYLIT:
		if(isslice(n->type)) {
			n->esc = EscNone;  // until proven otherwise
			e->noesc = list(e->noesc, n);
			n->escloopdepth = e->loopdepth;
			// Values make it to memory, lose track.
			for(ll=n->list; ll; ll=ll->next)
				escassign(e, &e->theSink, ll->n->right);
		} else {
			// Link values to array.
			for(ll=n->list; ll; ll=ll->next)
				escassign(e, n, ll->n->right);
		}
		break;

	case OSTRUCTLIT:
		// Link values to struct.
		for(ll=n->list; ll; ll=ll->next)
			escassign(e, n, ll->n->right);
		break;
	
	case OPTRLIT:
		n->esc = EscNone;  // until proven otherwise
		e->noesc = list(e->noesc, n);
		n->escloopdepth = e->loopdepth;
		// Contents make it to memory, lose track.
		escassign(e, &e->theSink, n->left);
		break;

	case OMAPLIT:
		n->esc = EscNone;  // until proven otherwise
		e->noesc = list(e->noesc, n);
		n->escloopdepth = e->loopdepth;
		// Keys and values make it to memory, lose track.
		for(ll=n->list; ll; ll=ll->next) {
			escassign(e, &e->theSink, ll->n->left);
			escassign(e, &e->theSink, ll->n->right);
		}
		break;
	
	case OCLOSURE:
		// Link addresses of captured variables to closure.
		for(ll=n->cvars; ll; ll=ll->next) {
			if(ll->n->op == OXXX)  // unnamed out argument; see dcl.c:/^funcargs
				continue;
			a = nod(OADDR, ll->n->closure, N);
			a->lineno = ll->n->lineno;
			a->escloopdepth = e->loopdepth;
			typecheck(&a, Erv);
			escassign(e, n, a);
		}
		// fallthrough
	case OADDR:
	case OMAKECHAN:
	case OMAKEMAP:
	case OMAKESLICE:
	case ONEW:
		n->escloopdepth = e->loopdepth;
		n->esc = EscNone;  // until proven otherwise
		e->noesc = list(e->noesc, n);
		break;
	}

	lineno = lno;
}

// Assert that expr somehow gets assigned to dst, if non nil.  for
// dst==nil, any name node expr still must be marked as being
// evaluated in curfn.	For expr==nil, dst must still be examined for
// evaluations inside it (e.g *f(x) = y)
static void
escassign(EscState *e, Node *dst, Node *src)
{
	int lno;

	if(isblank(dst) || dst == N || src == N || src->op == ONONAME || src->op == OXXX)
		return;

	if(debug['m'] > 1)
		print("%L:[%d] %S escassign: %hN(%hJ) = %hN(%hJ)\n", lineno, e->loopdepth,
		      (curfn && curfn->nname) ? curfn->nname->sym : S, dst, dst, src, src);

	setlineno(dst);
	
	// Analyze lhs of assignment.
	// Replace dst with e->theSink if we can't track it.
	switch(dst->op) {
	default:
		dump("dst", dst);
		fatal("escassign: unexpected dst");

	case OARRAYLIT:
	case OCLOSURE:
	case OCONV:
	case OCONVIFACE:
	case OCONVNOP:
	case OMAPLIT:
	case OSTRUCTLIT:
		break;

	case ONAME:
		if(dst->class == PEXTERN)
			dst = &e->theSink;
		break;
	case ODOT:	      // treat "dst.x  = src" as "dst = src"
		escassign(e, dst->left, src);
		return;
	case OINDEX:
		if(isfixedarray(dst->left->type)) {
			escassign(e, dst->left, src);
			return;
		}
		dst = &e->theSink;  // lose track of dereference
		break;
	case OIND:
	case ODOTPTR:
		dst = &e->theSink;  // lose track of dereference
		break;
	case OINDEXMAP:
		// lose track of key and value
		escassign(e, &e->theSink, dst->right);
		dst = &e->theSink;
		break;
	}

	lno = setlineno(src);
	e->pdepth++;

	switch(src->op) {
	case OADDR:	// dst = &x
	case OIND:	// dst = *x
	case ODOTPTR:	// dst = (*x).f
	case ONAME:
	case OPARAM:
	case ODDDARG:
	case OPTRLIT:
	case OARRAYLIT:
	case OMAPLIT:
	case OSTRUCTLIT:
	case OMAKECHAN:
	case OMAKEMAP:
	case OMAKESLICE:
	case ONEW:
	case OCLOSURE:
		escflows(e, dst, src);
		break;

	case ODOT:
		// A non-pointer escaping from a struct does not concern us.
		if(src->type && !haspointers(src->type))
			break;
		// fallthrough
	case OCONV:
	case OCONVIFACE:
	case OCONVNOP:
	case ODOTMETH:	// treat recv.meth as a value with recv in it, only happens in ODEFER and OPROC
			// iface.method already leaks iface in esccall, no need to put in extra ODOTINTER edge here
	case ODOTTYPE:
	case ODOTTYPE2:
	case OSLICE:
	case OSLICEARR:
		// Conversions, field access, slice all preserve the input value.
		escassign(e, dst, src->left);
		break;

	case OAPPEND:
		// Append returns first argument.
		escassign(e, dst, src->list->n);
		break;
	
	case OINDEX:
		// Index of array preserves input value.
		if(isfixedarray(src->left->type))
			escassign(e, dst, src->left);
		break;

	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:
		// Might be pointer arithmetic, in which case
		// the operands flow into the result.
		// TODO(rsc): Decide what the story is here.  This is unsettling.
		escassign(e, dst, src->left);
		escassign(e, dst, src->right);
		break;
	}

	e->pdepth--;
	lineno = lno;
}


// This is a bit messier than fortunate, pulled out of escassign's big
// switch for clarity.	We either have the paramnodes, which may be
// connected to other things throug flows or we have the parameter type
// nodes, which may be marked "noescape". Navigating the ast is slightly
// different for methods vs plain functions and for imported vs
// this-package
static void
esccall(EscState *e, Node *n)
{
	NodeList *ll, *lr;
	Node *a, *fn, *src;
	Type *t, *fntype;

	fn = N;
	switch(n->op) {
	default:
		fatal("esccall");

	case OCALLFUNC:
		fn = n->left;
		fntype = fn->type;
		break;

	case OCALLMETH:
		fn = n->left->right->sym->def;
		if(fn)
			fntype = fn->type;
		else
			fntype = n->left->type;
		break;

	case OCALLINTER:
		fntype = n->left->type;
		break;
	}

	ll = n->list;
	if(n->list != nil && n->list->next == nil) {
		a = n->list->n;
		if(a->type->etype == TSTRUCT && a->type->funarg) {
			// f(g()).
			// Since f's arguments are g's results and
			// all function results escape, we're done.
			ll = nil;
		}
	}
			
	if(fn && fn->op == ONAME && fn->class == PFUNC && fn->defn && fn->defn->nbody && fn->ntype && fn->defn->esc < EscFuncTagged) {
		// Local function in this round.  Incorporate into flow graph.
		if(fn->defn->esc == EscFuncUnknown)
			fatal("graph inconsistency");

		// Receiver.
		if(n->op != OCALLFUNC)
			escassign(e, fn->ntype->left->left, n->left->left);

		for(lr=fn->ntype->list; ll && lr; ll=ll->next, lr=lr->next) {
			src = ll->n;
			if(lr->n->isddd && !n->isddd) {
				// Introduce ODDDARG node to represent ... allocation.
				src = nod(ODDDARG, N, N);
				src->escloopdepth = e->loopdepth;
				src->lineno = n->lineno;
				src->esc = EscNone;  // until we find otherwise
				e->noesc = list(e->noesc, src);
				n->right = src;
			}
			if(lr->n->left != N)
				escassign(e, lr->n->left, src);
			if(src != ll->n)
				break;
		}
		// "..." arguments are untracked
		for(; ll; ll=ll->next)
			escassign(e, &e->theSink, ll->n);
		return;
	}

	// Imported or completely analyzed function.  Use the escape tags.
	if(n->op != OCALLFUNC) {
		t = getthisx(fntype)->type;
		if(!t->note || strcmp(t->note->s, safetag->s) != 0)
			escassign(e, &e->theSink, n->left->left);
	}
	for(t=getinargx(fntype)->type; ll; ll=ll->next) {
		src = ll->n;
		if(t->isddd && !n->isddd) {
			// Introduce ODDDARG node to represent ... allocation.
			src = nod(ODDDARG, N, N);
			src->escloopdepth = e->loopdepth;
			src->lineno = n->lineno;
			src->esc = EscNone;  // until we find otherwise
			e->noesc = list(e->noesc, src);
			n->right = src;
		}
		if(!t->note || strcmp(t->note->s, safetag->s) != 0)
			escassign(e, &e->theSink, src);
		if(src != ll->n)
			break;
		t = t->down;
	}
	// "..." arguments are untracked
	for(; ll; ll=ll->next)
		escassign(e, &e->theSink, ll->n);
}

// Store the link src->dst in dst, throwing out some quick wins.
static void
escflows(EscState *e, Node *dst, Node *src)
{
	if(dst == nil || src == nil || dst == src)
		return;

	// Don't bother building a graph for scalars.
	if(src->type && !haspointers(src->type))
		return;

	if(debug['m']>2)
		print("%L::flows:: %hN <- %hN\n", lineno, dst, src);

	if(dst->escflowsrc == nil) {
		e->dsts = list(e->dsts, dst);
		e->dstcount++;
	}
	e->edgecount++;

	dst->escflowsrc = list(dst->escflowsrc, src);
}

// Whenever we hit a reference node, the level goes up by one, and whenever
// we hit an OADDR, the level goes down by one. as long as we're on a level > 0
// finding an OADDR just means we're following the upstream of a dereference,
// so this address doesn't leak (yet).
// If level == 0, it means the /value/ of this node can reach the root of this flood.
// so if this node is an OADDR, it's argument should be marked as escaping iff
// it's currfn/e->loopdepth are different from the flood's root.
// Once an object has been moved to the heap, all of it's upstream should be considered
// escaping to the global scope.
static void
escflood(EscState *e, Node *dst)
{
	NodeList *l;

	switch(dst->op) {
	case ONAME:
	case OCLOSURE:
		break;
	default:
		return;
	}

	if(debug['m']>1)
		print("\nescflood:%d: dst %hN scope:%S[%d]\n", walkgen, dst,
		      (dst->curfn && dst->curfn->nname) ? dst->curfn->nname->sym : S,
		      dst->escloopdepth);

	for(l = dst->escflowsrc; l; l=l->next) {
		walkgen++;
		escwalk(e, 0, dst, l->n);
	}
}

static void
escwalk(EscState *e, int level, Node *dst, Node *src)
{
	NodeList *ll;
	int leaks;

	if(src->walkgen == walkgen)
		return;
	src->walkgen = walkgen;

	if(debug['m']>1)
		print("escwalk: level:%d depth:%d %.*s %hN(%hJ) scope:%S[%d]\n",
		      level, e->pdepth, e->pdepth, "\t\t\t\t\t\t\t\t\t\t", src, src,
		      (src->curfn && src->curfn->nname) ? src->curfn->nname->sym : S, src->escloopdepth);

	e->pdepth++;

	leaks = (level <= 0) && (dst->escloopdepth < src->escloopdepth);

	switch(src->op) {
	case ONAME:
		if(src->class == PPARAM && leaks && src->esc == EscNone) {
			src->esc = EscScope;
			if(debug['m'])
				warnl(src->lineno, "leaking param: %hN", src);
		}

		// Treat a PPARAMREF closure variable as equivalent to the
		// original variable.
		if(src->class == PPARAMREF) {
			if(leaks && debug['m'])
				warnl(src->lineno, "leaking closure reference %hN", src);
			escwalk(e, level, dst, src->closure);
		}
		break;

	case OPTRLIT:
	case OADDR:
		if(leaks) {
			src->esc = EscHeap;
			addrescapes(src->left);
			if(debug['m'])
				warnl(src->lineno, "%hN escapes to heap", src);
		}
		escwalk(e, level-1, dst, src->left);
		break;

	case OARRAYLIT:
		if(isfixedarray(src->type))
			break;
		// fall through
	case ODDDARG:
	case OMAKECHAN:
	case OMAKEMAP:
	case OMAKESLICE:
	case OMAPLIT:
	case ONEW:
	case OCLOSURE:
		if(leaks) {
			src->esc = EscHeap;
			if(debug['m'])
				warnl(src->lineno, "%hN escapes to heap", src);
		}
		break;

	case OINDEX:
		if(isfixedarray(src->type))
			break;
		// fall through
	case OSLICE:
	case ODOTPTR:
	case OINDEXMAP:
	case OIND:
		escwalk(e, level+1, dst, src->left);
	}

	for(ll=src->escflowsrc; ll; ll=ll->next)
		escwalk(e, level, dst, ll->n);

	e->pdepth--;
}

static void
esctag(EscState *e, Node *func)
{
	Node *savefn;
	NodeList *ll;
	
	func->esc = EscFuncTagged;
	
	// External functions must be assumed unsafe.
	if(func->nbody == nil)
		return;

	savefn = curfn;
	curfn = func;

	for(ll=curfn->dcl; ll; ll=ll->next) {
		if(ll->n->op != ONAME || ll->n->class != PPARAM)
			continue;

		switch (ll->n->esc) {
		case EscNone:	// not touched by escflood
			if(haspointers(ll->n->type)) // don't bother tagging for scalars
				ll->n->paramfld->note = safetag;
		case EscHeap:	// touched by escflood, moved to heap
		case EscScope:	// touched by escflood, value leaves scope
			break;
		}
	}

	curfn = savefn;
}
