aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/gc/esc.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd/gc/esc.c')
-rw-r--r--src/cmd/gc/esc.c1342
1 files changed, 0 insertions, 1342 deletions
diff --git a/src/cmd/gc/esc.c b/src/cmd/gc/esc.c
deleted file mode 100644
index 5b09c0b7fb..0000000000
--- a/src/cmd/gc/esc.c
+++ /dev/null
@@ -1,1342 +0,0 @@
-// 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)
- 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, Node *up);
-static void esc(EscState*, Node *n, Node *up);
-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*, Node *up);
-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;
-
- // If an analyzed function is recorded to return
- // pieces obtained via indirection from a parameter,
- // and later there is a call f(x) to that function,
- // we create a link funcParam <- x to record that fact.
- // The funcParam node is handled specially in escflood.
- Node funcParam;
-
- 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
- int recursive; // recursive function or group of mutually recursive functions.
-};
-
-static Strlit *tags[16];
-
-static Strlit*
-mktag(int mask)
-{
- Strlit *s;
- char buf[40];
-
- switch(mask&EscMask) {
- case EscNone:
- case EscReturn:
- break;
- default:
- fatal("escape mktag");
- }
-
- mask >>= EscBits;
-
- if(mask < nelem(tags) && tags[mask] != nil)
- return tags[mask];
-
- snprint(buf, sizeof buf, "esc:0x%x", mask);
- s = newstrlit(buf);
- if(mask < nelem(tags))
- tags[mask] = s;
- return s;
-}
-
-static int
-parsetag(Strlit *note)
-{
- int em;
-
- if(note == nil)
- return EscUnknown;
- if(strncmp(note->s, "esc:", 4) != 0)
- return EscUnknown;
- em = atoi(note->s + 4);
- if (em == 0)
- return EscNone;
- return EscReturn | (em << EscBits);
-}
-
-static void
-analyze(NodeList *all, int recursive)
-{
- NodeList *l;
- EscState es, *e;
-
- 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;
- e->recursive = recursive;
-
- e->funcParam.op = ONAME;
- e->funcParam.orig = &e->funcParam;
- e->funcParam.class = PAUTO;
- e->funcParam.sym = lookup(".param");
- e->funcParam.escloopdepth = 10000000;
-
- 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 upstream 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;
-
-// print("escfunc %N %s\n", func->nname, e->recursive?"(recursive)":"");
-
- 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:
- // out params are in a loopdepth between the sink and all local variables
- ll->n->escloopdepth = 0;
- break;
- case PPARAM:
- ll->n->escloopdepth = 1;
- if(ll->n->type && !haspointers(ll->n->type))
- break;
- if(curfn->nbody == nil && !curfn->noescape)
- ll->n->esc = EscHeap;
- else
- ll->n->esc = EscNone; // prime for escflood later
- e->noesc = list(e->noesc, ll->n);
- break;
- }
- }
-
- // in a mutually recursive group we lose track of the return values
- if(e->recursive)
- for(ll=curfn->dcl; ll; ll=ll->next)
- if(ll->n->op == ONAME && ll->n->class == PPARAMOUT)
- escflows(e, &e->theSink, ll->n);
-
- escloopdepthlist(e, curfn->nbody);
- esclist(e, curfn->nbody, curfn);
- 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, Node *up)
-{
- for(; l; l=l->next)
- esc(e, l->n, up);
-}
-
-static void
-esc(EscState *e, Node *n, Node *up)
-{
- int lno;
- NodeList *ll, *lr;
- Node *a, *v;
-
- if(n == N)
- return;
-
- lno = setlineno(n);
-
- // ninit logically runs at a different loopdepth than the rest of the for loop.
- esclist(e, n->ninit, n);
-
- if(n->op == OFOR || n->op == ORANGE)
- e->loopdepth++;
-
- // type switch variables have no ODCL.
- // process type switch as declaration.
- // must happen before processing of switch body,
- // so before recursion.
- if(n->op == OSWITCH && n->ntest && n->ntest->op == OTYPESW) {
- for(ll=n->list; ll; ll=ll->next) { // cases
- // ll->n->nname is the variable per case
- if(ll->n->nname)
- ll->n->nname->escloopdepth = e->loopdepth;
- }
- }
-
- esc(e, n->left, n);
- esc(e, n->right, n);
- esc(e, n->ntest, n);
- esc(e, n->nincr, n);
- esclist(e, n->nbody, n);
- esclist(e, n->nelse, n);
- esclist(e, n->list, n);
- esclist(e, n->rlist, n);
-
- 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 analysis missed or messed up a label: %+N", n);
-
- n->left->sym->label = nil;
- break;
-
- case ORANGE:
- // Everything but fixed array is a dereference.
- if(isfixedarray(n->type) && n->list && 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:
- // Filter out the following special case.
- //
- // func (b *Buffer) Foo() {
- // n, m := ...
- // b.buf = b.buf[n:m]
- // }
- //
- // This assignment is a no-op for escape analysis,
- // it does not store any new pointers into b that were not already there.
- // However, without this special case b will escape, because we assign to OIND/ODOTPTR.
- if((n->left->op == OIND || n->left->op == ODOTPTR) && n->left->left->op == ONAME && // dst is ONAME dereference
- (n->right->op == OSLICE || n->right->op == OSLICE3 || n->right->op == OSLICESTR) && // src is slice operation
- (n->right->left->op == OIND || n->right->left->op == ODOTPTR) && n->right->left->left->op == ONAME && // slice is applied to ONAME dereference
- n->left->left == n->right->left->left) { // dst and src reference the same base ONAME
- // Here we also assume that the statement will not contain calls,
- // that is, that order will move any calls to init.
- // Otherwise base ONAME value could change between the moments
- // when we evaluate it for dst and for src.
- //
- // Note, this optimization does not apply to OSLICEARR,
- // because it does introduce a new pointer into b that was not already there
- // (pointer to b itself). After such assignment, if b contents escape,
- // b escapes as well. If we ignore such OSLICEARR, we will conclude
- // that b does not escape when b contents do.
- if(debug['m']) {
- warnl(n->lineno, "%S ignoring self-assignment to %hN",
- (n->curfn && n->curfn->nname) ? n->curfn->nname->sym : S, n->left);
- }
- break;
- }
- 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 OCALLMETH:
- case OCALLFUNC:
- case OCALLINTER:
- esccall(e, n, up);
- break;
-
- case OAS2FUNC: // x,y = f()
- // esccall already done on n->rlist->n. tie it's escretval to n->list
- lr=n->rlist->n->escretval;
- for(ll=n->list; lr && ll; lr=lr->next, ll=ll->next)
- escassign(e, ll->n, lr->n);
- if(lr || ll)
- fatal("esc oas2func");
- break;
-
- case ORETURN:
- ll=n->list;
- if(count(n->list) == 1 && curfn->type->outtuple > 1) {
- // OAS2FUNC in disguise
- // esccall already done on n->list->n
- // tie n->list->n->escretval to curfn->dcl PPARAMOUT's
- ll = n->list->n->escretval;
- }
-
- for(lr = curfn->dcl; lr && ll; lr=lr->next) {
- if (lr->n->op != ONAME || lr->n->class != PPARAMOUT)
- continue;
- escassign(e, lr->n, ll->n);
- ll = ll->next;
- }
- if (ll != nil)
- fatal("esc return list");
- 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 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;
- // Link OSTRUCTLIT to OPTRLIT; if OPTRLIT escapes, OSTRUCTLIT elements do too.
- escassign(e, n, n->left);
- break;
-
- case OCALLPART:
- 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) {
- v = ll->n;
- if(v->op == OXXX) // unnamed out argument; see dcl.c:/^funcargs
- continue;
- a = v->closure;
- if(!v->byval) {
- a = nod(OADDR, a, N);
- a->lineno = v->lineno;
- a->escloopdepth = e->loopdepth;
- typecheck(&a, Erv);
- }
- escassign(e, n, a);
- }
- // fallthrough
- case OMAKECHAN:
- case OMAKEMAP:
- case OMAKESLICE:
- case ONEW:
- case OARRAYRUNESTR:
- case OARRAYBYTESTR:
- case OSTRARRAYRUNE:
- case OSTRARRAYBYTE:
- case ORUNESTR:
- n->escloopdepth = e->loopdepth;
- n->esc = EscNone; // until proven otherwise
- e->noesc = list(e->noesc, n);
- break;
-
- case OADDSTR:
- n->escloopdepth = e->loopdepth;
- n->esc = EscNone; // until proven otherwise
- e->noesc = list(e->noesc, n);
- // Arguments of OADDSTR do not escape.
- break;
-
- case OADDR:
- n->esc = EscNone; // until proven otherwise
- e->noesc = list(e->noesc, n);
- // current loop depth is an upper bound on actual loop depth
- // of addressed value.
- n->escloopdepth = e->loopdepth;
- // for &x, use loop depth of x if known.
- // it should always be known, but if not, be conservative
- // and keep the current loop depth.
- if(n->left->op == ONAME) {
- switch(n->left->class) {
- case PAUTO:
- if(n->left->escloopdepth != 0)
- n->escloopdepth = n->left->escloopdepth;
- break;
- case PPARAM:
- case PPARAMOUT:
- // PPARAM is loop depth 1 always.
- // PPARAMOUT is loop depth 0 for writes
- // but considered loop depth 1 for address-of,
- // so that writing the address of one result
- // to another (or the same) result makes the
- // first result move to the heap.
- n->escloopdepth = 1;
- break;
- }
- }
- 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;
- NodeList *ll;
-
- 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:
- case OPTRLIT:
- case OCALLPART:
- 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 OARRAYRUNESTR:
- case OARRAYBYTESTR:
- case OSTRARRAYRUNE:
- case OSTRARRAYBYTE:
- case OADDSTR:
- case ONEW:
- case OCLOSURE:
- case OCALLPART:
- case ORUNESTR:
- escflows(e, dst, src);
- break;
-
- case OCALLMETH:
- case OCALLFUNC:
- case OCALLINTER:
- // Flowing multiple returns to a single dst happens when
- // analyzing "go f(g())": here g() flows to sink (issue 4529).
- for(ll=src->escretval; ll; ll=ll->next)
- escflows(e, dst, ll->n);
- 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 OSLICE3:
- case OSLICEARR:
- case OSLICE3ARR:
- case OSLICESTR:
- // 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;
-}
-
-static int
-escassignfromtag(EscState *e, Strlit *note, NodeList *dsts, Node *src)
-{
- int em, em0;
-
- em = parsetag(note);
-
- if(em == EscUnknown) {
- escassign(e, &e->theSink, src);
- return em;
- }
-
- if(em == EscNone)
- return em;
-
- // If content inside parameter (reached via indirection)
- // escapes back to results, mark as such.
- if(em & EscContentEscapes)
- escassign(e, &e->funcParam, src);
-
- em0 = em;
- for(em >>= EscReturnBits; em && dsts; em >>= 1, dsts=dsts->next)
- if(em & 1)
- escassign(e, dsts->n, src);
-
- if (em != 0 && dsts == nil)
- fatal("corrupt esc tag %Z or messed up escretval list\n", note);
- return em0;
-}
-
-// This is a bit messier than fortunate, pulled out of esc's big
-// switch for clarity. We either have the paramnodes, which may be
-// connected to other things through 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, Node *up)
-{
- NodeList *ll, *lr;
- Node *a, *fn, *src;
- Type *t, *fntype;
- char buf[40];
- int i;
-
- 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()).
- ll = a->escretval;
- }
-
- if(fn && fn->op == ONAME && fn->class == PFUNC && fn->defn && fn->defn->nbody && fn->ntype && fn->defn->esc < EscFuncTagged) {
- // function in same mutually recursive group. Incorporate into flow graph.
-// print("esc local fn: %N\n", fn->ntype);
- if(fn->defn->esc == EscFuncUnknown || n->escretval != nil)
- fatal("graph inconsistency");
-
- // set up out list on this call node
- for(lr=fn->ntype->rlist; lr; lr=lr->next)
- n->escretval = list(n->escretval, lr->n->left); // type.rlist -> dclfield -> ONAME (PPARAMOUT)
-
- // 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->type = typ(TARRAY);
- src->type->type = lr->n->type->type;
- src->type->bound = count(ll);
- src->type = ptrto(src->type); // make pointer so it will be tracked
- 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->escretval != nil)
- fatal("esc already decorated call %+N\n", n);
-
- // set up out list on this call node with dummy auto ONAMES in the current (calling) function.
- i = 0;
- for(t=getoutargx(fntype)->type; t; t=t->down) {
- src = nod(ONAME, N, N);
- snprint(buf, sizeof buf, ".dum%d", i++);
- src->sym = lookup(buf);
- src->type = t->type;
- src->class = PAUTO;
- src->curfn = curfn;
- src->escloopdepth = e->loopdepth;
- src->used = 1;
- src->lineno = n->lineno;
- n->escretval = list(n->escretval, src);
- }
-
-// print("esc analyzed fn: %#N (%+T) returning (%+H)\n", fn, fntype, n->escretval);
-
- // Receiver.
- if(n->op != OCALLFUNC) {
- t = getthisx(fntype)->type;
- src = n->left->left;
- if(haspointers(t->type))
- escassignfromtag(e, t->note, n->escretval, src);
- }
-
- 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->type = typ(TARRAY);
- src->type->type = t->type->type;
- src->type->bound = count(ll);
- src->type = ptrto(src->type); // make pointer so it will be tracked
- src->esc = EscNone; // until we find otherwise
- e->noesc = list(e->noesc, src);
- n->right = src;
- }
- if(haspointers(t->type)) {
- if(escassignfromtag(e, t->note, n->escretval, src) == EscNone && up->op != ODEFER && up->op != OPROC) {
- a = src;
- while(a->op == OCONVNOP)
- a = a->left;
- switch(a->op) {
- case OCALLPART:
- case OCLOSURE:
- case ODDDARG:
- case OARRAYLIT:
- case OPTRLIT:
- case OSTRUCTLIT:
- // The callee has already been analyzed, so its arguments have esc tags.
- // The argument is marked as not escaping at all.
- // Record that fact so that any temporary used for
- // synthesizing this expression can be reclaimed when
- // the function returns.
- // This 'noescape' is even stronger than the usual esc == EscNone.
- // src->esc == EscNone means that src does not escape the current function.
- // src->noescape = 1 here means that src does not escape this statement
- // in the current function.
- a->noescape = 1;
- break;
- }
- }
- }
- 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);
- }
-}
-
-// There appear to be some loops in the escape graph, causing
-// arbitrary recursion into deeper and deeper levels.
-// Cut this off safely by making minLevel sticky: once you
-// get that deep, you cannot go down any further but you also
-// cannot go up any further. This is a conservative fix.
-// Making minLevel smaller (more negative) would handle more
-// complex chains of indirections followed by address-of operations,
-// at the cost of repeating the traversal once for each additional
-// allowed level when a loop is encountered. Using -2 suffices to
-// pass all the tests we have written so far, which we assume matches
-// the level of complexity we want the escape analysis code to handle.
-#define MinLevel (-2)
-/*c2go enum { MinLevel = -2 };*/
-
-static void
-escwalk(EscState *e, int level, Node *dst, Node *src)
-{
- NodeList *ll;
- int leaks, newlevel;
-
- if(src->walkgen == walkgen && src->esclevel <= level)
- return;
- src->walkgen = walkgen;
- src->esclevel = level;
-
- 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++;
-
- // Input parameter flowing to output parameter?
- if(dst->op == ONAME && dst->class == PPARAMOUT && dst->vargen <= 20) {
- if(src->op == ONAME && src->class == PPARAM && src->curfn == dst->curfn && src->esc != EscScope && src->esc != EscHeap) {
- if(level == 0) {
- if(debug['m'])
- warnl(src->lineno, "leaking param: %hN to result %S", src, dst->sym);
- if((src->esc&EscMask) != EscReturn)
- src->esc = EscReturn;
- src->esc |= 1<<((dst->vargen-1) + EscReturnBits);
- goto recurse;
- } else if(level > 0) {
- if(debug['m'])
- warnl(src->lineno, "%N leaking param %hN content to result %S", src->curfn->nname, src, dst->sym);
- if((src->esc&EscMask) != EscReturn)
- src->esc = EscReturn;
- src->esc |= EscContentEscapes;
- goto recurse;
- }
- }
- }
-
- // The second clause is for values pointed at by an object passed to a call
- // that returns something reached via indirect from the object.
- // We don't know which result it is or how many indirects, so we treat it as leaking.
- leaks = level <= 0 && dst->escloopdepth < src->escloopdepth ||
- level < 0 && dst == &e->funcParam && haspointers(src->type);
-
- switch(src->op) {
- case ONAME:
- if(src->class == PPARAM && (leaks || dst->escloopdepth < 0) && src->esc != EscHeap) {
- 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);
- }
- newlevel = level;
- if(level > MinLevel)
- newlevel--;
- escwalk(e, newlevel, dst, src->left);
- break;
-
- case OARRAYLIT:
- if(isfixedarray(src->type))
- break;
- // fall through
- case ODDDARG:
- case OMAKECHAN:
- case OMAKEMAP:
- case OMAKESLICE:
- case OARRAYRUNESTR:
- case OARRAYBYTESTR:
- case OSTRARRAYRUNE:
- case OSTRARRAYBYTE:
- case OADDSTR:
- case OMAPLIT:
- case ONEW:
- case OCLOSURE:
- case OCALLPART:
- case ORUNESTR:
- if(leaks) {
- src->esc = EscHeap;
- if(debug['m'])
- warnl(src->lineno, "%hN escapes to heap", src);
- }
- break;
-
- case ODOT:
- case OSLICE:
- case OSLICEARR:
- case OSLICE3:
- case OSLICE3ARR:
- case OSLICESTR:
- escwalk(e, level, dst, src->left);
- break;
-
- case OINDEX:
- if(isfixedarray(src->left->type)) {
- escwalk(e, level, dst, src->left);
- break;
- }
- // fall through
- case ODOTPTR:
- case OINDEXMAP:
- case OIND:
- newlevel = level;
- if(level > MinLevel)
- newlevel++;
- escwalk(e, newlevel, dst, src->left);
- }
-
-recurse:
- 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;
- Type *t;
-
- USED(e);
- func->esc = EscFuncTagged;
-
- // External functions are assumed unsafe,
- // unless //go:noescape is given before the declaration.
- if(func->nbody == nil) {
- if(func->noescape) {
- for(t=getinargx(func->type)->type; t; t=t->down)
- if(haspointers(t->type))
- t->note = mktag(EscNone);
- }
- 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&EscMask) {
- case EscNone: // not touched by escflood
- case EscReturn:
- if(haspointers(ll->n->type)) // don't bother tagging for scalars
- ll->n->paramfld->note = mktag(ll->n->esc);
- break;
- case EscHeap: // touched by escflood, moved to heap
- case EscScope: // touched by escflood, value leaves scope
- break;
- }
- }
-
- curfn = savefn;
-}