]> Cypherpunks.ru repositories - gostls13.git/commitdiff
cmd/gc: escape analysis to track flow of in to out parameters.
authorLuuk van Dijk <lvd@golang.org>
Mon, 29 Oct 2012 12:38:21 +0000 (13:38 +0100)
committerLuuk van Dijk <lvd@golang.org>
Mon, 29 Oct 2012 12:38:21 +0000 (13:38 +0100)
includes step 0: synthesize outparams, from 6600044
includes step 1,2: give outparams loopdepth 0 and verify unchanged results
         generate esc:$mask tags, but still tie to sink if a param has mask != 0
from 6610054

adds final steps:
- have esccall generate n->escretval, a list of nodes the function results flow to
- use these in esccall and ORETURN/OAS2FUNC/and f(g())
- only tie parameters to sink if tag is absent, otherwise according to mask, tie them to escretval

R=rsc, bradfitz
CC=dave, gobot, golang-dev, iant, rsc
https://golang.org/cl/6741044

src/cmd/gc/esc.c
src/cmd/gc/go.h
test/escape2.go
test/escape5.go [new file with mode: 0644]

index a42027ea5f7e55e8c8685cb99aaa22c081f05868..a2bcbae8fe204227be2ab8c3d9ff74bab6d1b739 100644 (file)
@@ -209,9 +209,10 @@ struct EscState {
        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] = { nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil };
+static Strlit *tags[16];
 
 static Strlit*
 mktag(int mask)
@@ -260,8 +261,6 @@ analyze(NodeList *all, int recursive)
        NodeList *l;
        EscState es, *e;
        
-       USED(recursive);
-
        memset(&es, 0, sizeof es);
        e = &es;
        e->theSink.op = ONAME;
@@ -269,6 +268,7 @@ analyze(NodeList *all, int recursive)
        e->theSink.class = PEXTERN;
        e->theSink.sym = lookup(".sink");
        e->theSink.escloopdepth = -1;
+       e->recursive = recursive;
 
        for(l=all; l; l=l->next)
                if(l->n->op == ODCLFUNC)
@@ -308,6 +308,8 @@ escfunc(EscState *e, Node *func)
        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;
@@ -335,6 +337,12 @@ escfunc(EscState *e, Node *func)
                }
        }
 
+       // 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 = savefn;
@@ -450,7 +458,7 @@ esc(EscState *e, Node *n)
                }
                // See case OLABEL in escloopdepth above
                // else if(n->left->sym->label == nil)
-               //      fatal("escape anaylysis missed or messed up a label: %+N", n);
+               //      fatal("escape analysis missed or messed up a label: %+N", n);
 
                n->left->sym->label = nil;
                break;
@@ -506,13 +514,30 @@ esc(EscState *e, Node *n)
                        escassign(e, &e->theSink, ll->n);
                break;
 
+       case OCALLMETH:
+       case OCALLFUNC:
+       case OCALLINTER:
+               esccall(e, n);
+               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
-                       break;
+                       // esccall already done on n->list->n
+                       // tie n->list->n->escretval to curfn->dcl PPARAMOUT's
+                       ll = n->list->n->escretval;
                }
 
-               ll=n->list;
                for(lr = curfn->dcl; lr && ll; lr=lr->next) {
                        if (lr->n->op != ONAME || lr->n->class != PPARAMOUT)
                                continue;
@@ -534,12 +559,6 @@ esc(EscState *e, Node *n)
                                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:
@@ -693,6 +712,14 @@ escassign(EscState *e, Node *dst, Node *src)
                escflows(e, dst, src);
                break;
 
+       case OCALLMETH:
+       case OCALLFUNC:
+       case OCALLINTER:
+               if(count(src->escretval) != 1)
+                       fatal("escassign from call %+N", src);
+               escflows(e, dst, src->escretval->n);
+               break;
+
        case ODOT:
                // A non-pointer escaping from a struct does not concern us.
                if(src->type && !haspointers(src->type))
@@ -748,6 +775,26 @@ escassign(EscState *e, Node *dst, Node *src)
        lineno = lno;
 }
 
+static void
+escassignfromtag(EscState *e, Strlit *note, NodeList *dsts, Node *src)
+{
+       int em;
+       
+       em = parsetag(note);
+       
+       if(em == EscUnknown) {
+               escassign(e, &e->theSink, src);
+               return;
+       }
+               
+       for(em >>= EscBits; 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);
+}
+
 // 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 throug flows or we have the parameter type
@@ -760,6 +807,8 @@ esccall(EscState *e, Node *n)
        NodeList *ll, *lr;
        Node *a, *fn, *src;
        Type *t, *fntype;
+       char buf[40];
+       int i;
 
        fn = N;
        switch(n->op) {
@@ -787,19 +836,20 @@ esccall(EscState *e, Node *n)
        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(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) {
-               // Local function in this round.  Incorporate into flow graph.
-               if(fn->defn->esc == EscFuncUnknown)
+               // 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);
@@ -823,15 +873,35 @@ esccall(EscState *e, Node *n)
                // "..." 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(parsetag(t->note) != EscNone)
-                       escassign(e, &e->theSink, n->left->left);
+       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)
+               escassignfromtag(e, getthisx(fntype)->type->note, n->escretval, n->left->left);
+       
        for(t=getinargx(fntype)->type; ll; ll=ll->next) {
                src = ll->n;
                if(t->isddd && !n->isddd) {
@@ -843,8 +913,7 @@ esccall(EscState *e, Node *n)
                        e->noesc = list(e->noesc, src);
                        n->right = src;
                }
-               if(parsetag(t->note) != EscNone)
-                       escassign(e, &e->theSink, src);
+               escassignfromtag(e, t->note, n->escretval, src);
                if(src != ll->n)
                        break;
                t = t->down;
index c1e637120c2de0c11203d94607e10135b7c48c05..d92dd406112c264040a7340617df20f5b00f3767 100644 (file)
@@ -306,7 +306,8 @@ struct      Node
 
        // Escape analysis.
        NodeList* escflowsrc;   // flow(this, src)
-       int     escloopdepth;   // -1: global, 0: not set, function top level:1, increased inside function for every loop or label to mark scopes
+       NodeList* escretval;    // on OCALLxxx, list of dummy return values
+       int     escloopdepth;   // -1: global, 0: return variables, 1:function top level, increased inside function for every loop or label to mark scopes
 
        Sym*    sym;            // various
        int32   vargen;         // unique name for OTYPE/ONAME
index 8db12d99131f77862e1dec65195eff60a8eca12e..bfc90ecb411511563ec500731f48a3b7d7a9cf0d 100644 (file)
@@ -561,12 +561,21 @@ func myprint1(y *int, x ...interface{}) *interface{} { // ERROR "y does not esca
        return &x[0] // ERROR "&x.0. escapes to heap"
 }
 
-func foo75(z *int) { // ERROR "leaking param: z"
+func foo75(z *int) { // ERROR "z does not escape"
        myprint(z, 1, 2, 3) // ERROR "[.][.][.] argument does not escape"
 }
 
 func foo75a(z *int) { // ERROR "z does not escape"
-       myprint1(z, 1, 2, 3) // ERROR "[.][.][.] argument escapes to heap"
+       myprint1(z, 1, 2, 3) // ERROR "[.][.][.] argument does not escape"
+}
+
+func foo75esc(z *int) { // ERROR "leaking param: z"
+       gxx = myprint(z, 1, 2, 3) // ERROR "[.][.][.] argument does not escape"
+}
+
+func foo75aesc(z *int) { // ERROR "z does not escape"
+       var ppi **interface{}   // assignments to pointer dereferences lose track
+       *ppi = myprint1(z, 1, 2, 3) // ERROR "[.][.][.] argument escapes to heap"
 }
 
 func foo76(z *int) { // ERROR "leaking param: z"
@@ -574,7 +583,7 @@ func foo76(z *int) { // ERROR "leaking param: z"
 }
 
 func foo76a(z *int) { // ERROR "leaking param: z"
-       myprint1(nil, z) // ERROR "[.][.][.] argument escapes to heap"
+       myprint1(nil, z) // ERROR "[.][.][.] argument does not escape"
 }
 
 func foo76b() {
@@ -582,7 +591,7 @@ func foo76b() {
 }
 
 func foo76c() {
-       myprint1(nil, 1, 2, 3) // ERROR "[.][.][.] argument escapes to heap"
+       myprint1(nil, 1, 2, 3) // ERROR "[.][.][.] argument does not escape"
 }
 
 func foo76d() {
@@ -590,7 +599,7 @@ func foo76d() {
 }
 
 func foo76e() {
-       defer myprint1(nil, 1, 2, 3) // ERROR "[.][.][.] argument escapes to heap"
+       defer myprint1(nil, 1, 2, 3) // ERROR "[.][.][.] argument does not escape"
 }
 
 func foo76f() {
@@ -610,10 +619,15 @@ func foo77(z []interface{}) { // ERROR "z does not escape"
        myprint(nil, z...) // z does not escape
 }
 
-func foo77a(z []interface{}) { // ERROR "leaking param: z"
+func foo77a(z []interface{}) { // ERROR "z does not escape"
        myprint1(nil, z...)
 }
 
+func foo77b(z []interface{}) { // ERROR "leaking param: z"
+       var ppi **interface{}
+       *ppi = myprint1(nil, z...)
+}
+
 func foo78(z int) *int { // ERROR "moved to heap: z"
        return &z // ERROR "&z escapes to heap"
 }
diff --git a/test/escape5.go b/test/escape5.go
new file mode 100644 (file)
index 0000000..22c324f
--- /dev/null
@@ -0,0 +1,119 @@
+// errorcheck -0 -m -l
+
+// Copyright 2012 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.
+
+// Test, using compiler diagnostic flags, that the escape analysis is working.
+// Compiles but does not run.  Inlining is disabled.
+
+package foo
+
+func noleak(p *int) int { // ERROR "p does not escape"
+       return *p
+}
+
+func leaktoret(p *int) *int { // ERROR "leaking param: p to result"
+       return p
+}
+
+func leaktoret2(p *int) (*int, *int) { // ERROR "leaking param: p to result .anon1" "leaking param: p to result .anon2"
+       return p, p
+}
+
+func leaktoret22(p, q *int) (*int, *int) { // ERROR "leaking param: p to result .anon2" "leaking param: q to result .anon3"
+       return p, q
+}
+
+func leaktoret22b(p, q *int) (*int, *int) { // ERROR "leaking param: p to result .anon3" "leaking param: q to result .anon2"
+       return leaktoret22(q, p)
+}
+
+func leaktoret22c(p, q *int) (*int, *int) { // ERROR "leaking param: p to result .anon3" "leaking param: q to result .anon2"
+       r, s := leaktoret22(q, p)
+       return r, s
+}
+
+func leaktoret22d(p, q *int) (r, s *int) { // ERROR "leaking param: p to result s" "leaking param: q to result r"
+       r, s = leaktoret22(q, p)
+       return
+}
+
+func leaktoret22e(p, q *int) (r, s *int) { // ERROR "leaking param: p to result s" "leaking param: q to result r"
+       r, s = leaktoret22(q, p)
+       return r, s
+}
+
+func leaktoret22f(p, q *int) (r, s *int) { // ERROR "leaking param: p to result s" "leaking param: q to result r"
+       rr, ss := leaktoret22(q, p)
+       return rr, ss
+}
+
+var gp *int
+
+func leaktosink(p *int) *int { // ERROR "leaking param: p"
+       gp = p
+       return p
+}
+
+func f1() {
+       var x int
+       p := noleak(&x) // ERROR "&x does not escape"
+       _ = p
+}
+
+func f2() {
+       var x int
+       p := leaktoret(&x) // ERROR "&x does not escape"
+       _ = p
+}
+
+func f3() {
+       var x int          // ERROR "moved to heap: x"
+       p := leaktoret(&x) // ERROR "&x escapes to heap"
+       gp = p
+}
+
+func f4() {
+       var x int              // ERROR "moved to heap: x"
+       p, q := leaktoret2(&x) // ERROR "&x escapes to heap"
+       gp = p
+       gp = q
+}
+
+func f5() {
+       var x int
+       leaktoret22(leaktoret2(&x)) // ERROR "&x does not escape"
+}
+
+func f6() {
+       var x int                               // ERROR "moved to heap: x"
+       px1, px2 := leaktoret22(leaktoret2(&x)) // ERROR "&x escapes to heap"
+       gp = px1
+       _ = px2
+}
+
+type T struct{ x int }
+
+func (t *T) Foo(u int) (*T, bool) { // ERROR "leaking param: t to result"
+       t.x += u
+       return t, true
+}
+
+func f7() *T {
+       r, _ := new(T).Foo(42) // ERROR "new.T. escapes to heap"
+       return r
+}
+
+func leakrecursive1(p, q *int) (*int, *int) { // ERROR "leaking param: p" "leaking param: q"
+       return leakrecursive2(q, p)
+}
+
+func leakrecursive2(p, q *int) (*int, *int) { // ERROR "leaking param: p" "leaking param: q"
+       if *p > *q {
+               return leakrecursive1(q, p)
+       }
+       // without this, leakrecursive? are safe for p and q, b/c in fact their graph does not have leaking edges.
+       return p, q
+}
+