diff options
Diffstat (limited to 'src/cmd/compile/internal/gc/escape.go')
-rw-r--r-- | src/cmd/compile/internal/gc/escape.go | 1260 |
1 files changed, 880 insertions, 380 deletions
diff --git a/src/cmd/compile/internal/gc/escape.go b/src/cmd/compile/internal/gc/escape.go index 618bdf78e2..783bc8c41d 100644 --- a/src/cmd/compile/internal/gc/escape.go +++ b/src/cmd/compile/internal/gc/escape.go @@ -5,6 +5,8 @@ package gc import ( + "cmd/compile/internal/base" + "cmd/compile/internal/ir" "cmd/compile/internal/logopt" "cmd/compile/internal/types" "cmd/internal/src" @@ -84,7 +86,7 @@ import ( type Escape struct { allLocs []*EscLocation - curfn *Node + curfn ir.Node // loopDepth counts the current loop nesting depth within // curfn. It increments within each "for" loop and at each @@ -99,8 +101,8 @@ type Escape struct { // An EscLocation represents an abstract location that stores a Go // variable. type EscLocation struct { - n *Node // represented variable or expression, if any - curfn *Node // enclosing function + n ir.Node // represented variable or expression, if any + curfn ir.Node // enclosing function edges []EscEdge // incoming edges loopDepth int // loopDepth at declaration @@ -140,12 +142,47 @@ type EscEdge struct { notes *EscNote } +func init() { + ir.EscFmt = escFmt +} + +// escFmt is called from node printing to print information about escape analysis results. +func escFmt(n ir.Node, short bool) string { + text := "" + switch n.Esc() { + case EscUnknown: + break + + case EscHeap: + text = "esc(h)" + + case EscNone: + text = "esc(no)" + + case EscNever: + if !short { + text = "esc(N)" + } + + default: + text = fmt.Sprintf("esc(%d)", n.Esc()) + } + + if e, ok := n.Opt().(*EscLocation); ok && e.loopDepth != 0 { + if text != "" { + text += " " + } + text += fmt.Sprintf("ld(%d)", e.loopDepth) + } + return text +} + // escapeFuncs performs escape analysis on a minimal batch of // functions. -func escapeFuncs(fns []*Node, recursive bool) { +func escapeFuncs(fns []ir.Node, recursive bool) { for _, fn := range fns { - if fn.Op != ODCLFUNC { - Fatalf("unexpected node: %v", fn) + if fn.Op() != ir.ODCLFUNC { + base.Fatalf("unexpected node: %v", fn) } } @@ -165,40 +202,40 @@ func escapeFuncs(fns []*Node, recursive bool) { e.finish(fns) } -func (e *Escape) initFunc(fn *Node) { - if fn.Op != ODCLFUNC || fn.Esc != EscFuncUnknown { - Fatalf("unexpected node: %v", fn) +func (e *Escape) initFunc(fn ir.Node) { + if fn.Op() != ir.ODCLFUNC || fn.Esc() != EscFuncUnknown { + base.Fatalf("unexpected node: %v", fn) } - fn.Esc = EscFuncPlanned - if Debug.m > 3 { - Dump("escAnalyze", fn) + fn.SetEsc(EscFuncPlanned) + if base.Flag.LowerM > 3 { + ir.Dump("escAnalyze", fn) } e.curfn = fn e.loopDepth = 1 // Allocate locations for local variables. - for _, dcl := range fn.Func.Dcl { - if dcl.Op == ONAME { + for _, dcl := range fn.Func().Dcl { + if dcl.Op() == ir.ONAME { e.newLoc(dcl, false) } } } -func (e *Escape) walkFunc(fn *Node) { - fn.Esc = EscFuncStarted +func (e *Escape) walkFunc(fn ir.Node) { + fn.SetEsc(EscFuncStarted) // Identify labels that mark the head of an unstructured loop. - inspectList(fn.Nbody, func(n *Node) bool { - switch n.Op { - case OLABEL: - n.Sym.Label = asTypesNode(&nonlooping) + ir.InspectList(fn.Body(), func(n ir.Node) bool { + switch n.Op() { + case ir.OLABEL: + n.Sym().Label = nonlooping - case OGOTO: + case ir.OGOTO: // If we visited the label before the goto, // then this is a looping label. - if n.Sym.Label == asTypesNode(&nonlooping) { - n.Sym.Label = asTypesNode(&looping) + if n.Sym().Label == nonlooping { + n.Sym().Label = looping } } @@ -207,7 +244,7 @@ func (e *Escape) walkFunc(fn *Node) { e.curfn = fn e.loopDepth = 1 - e.block(fn.Nbody) + e.block(fn.Body()) } // Below we implement the methods for walking the AST and recording @@ -237,172 +274,172 @@ func (e *Escape) walkFunc(fn *Node) { // } // stmt evaluates a single Go statement. -func (e *Escape) stmt(n *Node) { +func (e *Escape) stmt(n ir.Node) { if n == nil { return } lno := setlineno(n) defer func() { - lineno = lno + base.Pos = lno }() - if Debug.m > 2 { - fmt.Printf("%v:[%d] %v stmt: %v\n", linestr(lineno), e.loopDepth, funcSym(e.curfn), n) + if base.Flag.LowerM > 2 { + fmt.Printf("%v:[%d] %v stmt: %v\n", base.FmtPos(base.Pos), e.loopDepth, funcSym(e.curfn), n) } - e.stmts(n.Ninit) + e.stmts(n.Init()) - switch n.Op { + switch n.Op() { default: - Fatalf("unexpected stmt: %v", n) + base.Fatalf("unexpected stmt: %v", n) - case ODCLCONST, ODCLTYPE, OEMPTY, OFALL, OINLMARK: + case ir.ODCLCONST, ir.ODCLTYPE, ir.OEMPTY, ir.OFALL, ir.OINLMARK: // nop - case OBREAK, OCONTINUE, OGOTO: + case ir.OBREAK, ir.OCONTINUE, ir.OGOTO: // TODO(mdempsky): Handle dead code? - case OBLOCK: - e.stmts(n.List) + case ir.OBLOCK: + e.stmts(n.List()) - case ODCL: + case ir.ODCL: // Record loop depth at declaration. - if !n.Left.isBlank() { - e.dcl(n.Left) + if !ir.IsBlank(n.Left()) { + e.dcl(n.Left()) } - case OLABEL: - switch asNode(n.Sym.Label) { - case &nonlooping: - if Debug.m > 2 { - fmt.Printf("%v:%v non-looping label\n", linestr(lineno), n) + case ir.OLABEL: + switch ir.AsNode(n.Sym().Label) { + case nonlooping: + if base.Flag.LowerM > 2 { + fmt.Printf("%v:%v non-looping label\n", base.FmtPos(base.Pos), n) } - case &looping: - if Debug.m > 2 { - fmt.Printf("%v: %v looping label\n", linestr(lineno), n) + case looping: + if base.Flag.LowerM > 2 { + fmt.Printf("%v: %v looping label\n", base.FmtPos(base.Pos), n) } e.loopDepth++ default: - Fatalf("label missing tag") + base.Fatalf("label missing tag") } - n.Sym.Label = nil + n.Sym().Label = nil - case OIF: - e.discard(n.Left) - e.block(n.Nbody) - e.block(n.Rlist) + case ir.OIF: + e.discard(n.Left()) + e.block(n.Body()) + e.block(n.Rlist()) - case OFOR, OFORUNTIL: + case ir.OFOR, ir.OFORUNTIL: e.loopDepth++ - e.discard(n.Left) - e.stmt(n.Right) - e.block(n.Nbody) + e.discard(n.Left()) + e.stmt(n.Right()) + e.block(n.Body()) e.loopDepth-- - case ORANGE: + case ir.ORANGE: // for List = range Right { Nbody } e.loopDepth++ - ks := e.addrs(n.List) - e.block(n.Nbody) + ks := e.addrs(n.List()) + e.block(n.Body()) e.loopDepth-- // Right is evaluated outside the loop. k := e.discardHole() if len(ks) >= 2 { - if n.Right.Type.IsArray() { + if n.Right().Type().IsArray() { k = ks[1].note(n, "range") } else { k = ks[1].deref(n, "range-deref") } } - e.expr(e.later(k), n.Right) + e.expr(e.later(k), n.Right()) - case OSWITCH: - typesw := n.Left != nil && n.Left.Op == OTYPESW + case ir.OSWITCH: + typesw := n.Left() != nil && n.Left().Op() == ir.OTYPESW var ks []EscHole - for _, cas := range n.List.Slice() { // cases - if typesw && n.Left.Left != nil { - cv := cas.Rlist.First() + for _, cas := range n.List().Slice() { // cases + if typesw && n.Left().Left() != nil { + cv := cas.Rlist().First() k := e.dcl(cv) // type switch variables have no ODCL. - if cv.Type.HasPointers() { - ks = append(ks, k.dotType(cv.Type, cas, "switch case")) + if cv.Type().HasPointers() { + ks = append(ks, k.dotType(cv.Type(), cas, "switch case")) } } - e.discards(cas.List) - e.block(cas.Nbody) + e.discards(cas.List()) + e.block(cas.Body()) } if typesw { - e.expr(e.teeHole(ks...), n.Left.Right) + e.expr(e.teeHole(ks...), n.Left().Right()) } else { - e.discard(n.Left) + e.discard(n.Left()) } - case OSELECT: - for _, cas := range n.List.Slice() { - e.stmt(cas.Left) - e.block(cas.Nbody) + case ir.OSELECT: + for _, cas := range n.List().Slice() { + e.stmt(cas.Left()) + e.block(cas.Body()) } - case OSELRECV: - e.assign(n.Left, n.Right, "selrecv", n) - case OSELRECV2: - e.assign(n.Left, n.Right, "selrecv", n) - e.assign(n.List.First(), nil, "selrecv", n) - case ORECV: + case ir.OSELRECV: + e.assign(n.Left(), n.Right(), "selrecv", n) + case ir.OSELRECV2: + e.assign(n.Left(), n.Right(), "selrecv", n) + e.assign(n.List().First(), nil, "selrecv", n) + case ir.ORECV: // TODO(mdempsky): Consider e.discard(n.Left). e.exprSkipInit(e.discardHole(), n) // already visited n.Ninit - case OSEND: - e.discard(n.Left) - e.assignHeap(n.Right, "send", n) - - case OAS, OASOP: - e.assign(n.Left, n.Right, "assign", n) - - case OAS2: - for i, nl := range n.List.Slice() { - e.assign(nl, n.Rlist.Index(i), "assign-pair", n) - } - - case OAS2DOTTYPE: // v, ok = x.(type) - e.assign(n.List.First(), n.Right, "assign-pair-dot-type", n) - e.assign(n.List.Second(), nil, "assign-pair-dot-type", n) - case OAS2MAPR: // v, ok = m[k] - e.assign(n.List.First(), n.Right, "assign-pair-mapr", n) - e.assign(n.List.Second(), nil, "assign-pair-mapr", n) - case OAS2RECV: // v, ok = <-ch - e.assign(n.List.First(), n.Right, "assign-pair-receive", n) - e.assign(n.List.Second(), nil, "assign-pair-receive", n) - - case OAS2FUNC: - e.stmts(n.Right.Ninit) - e.call(e.addrs(n.List), n.Right, nil) - case ORETURN: - results := e.curfn.Type.Results().FieldSlice() - for i, v := range n.List.Slice() { - e.assign(asNode(results[i].Nname), v, "return", n) - } - case OCALLFUNC, OCALLMETH, OCALLINTER, OCLOSE, OCOPY, ODELETE, OPANIC, OPRINT, OPRINTN, ORECOVER: + case ir.OSEND: + e.discard(n.Left()) + e.assignHeap(n.Right(), "send", n) + + case ir.OAS, ir.OASOP: + e.assign(n.Left(), n.Right(), "assign", n) + + case ir.OAS2: + for i, nl := range n.List().Slice() { + e.assign(nl, n.Rlist().Index(i), "assign-pair", n) + } + + case ir.OAS2DOTTYPE: // v, ok = x.(type) + e.assign(n.List().First(), n.Right(), "assign-pair-dot-type", n) + e.assign(n.List().Second(), nil, "assign-pair-dot-type", n) + case ir.OAS2MAPR: // v, ok = m[k] + e.assign(n.List().First(), n.Right(), "assign-pair-mapr", n) + e.assign(n.List().Second(), nil, "assign-pair-mapr", n) + case ir.OAS2RECV: // v, ok = <-ch + e.assign(n.List().First(), n.Right(), "assign-pair-receive", n) + e.assign(n.List().Second(), nil, "assign-pair-receive", n) + + case ir.OAS2FUNC: + e.stmts(n.Right().Init()) + e.call(e.addrs(n.List()), n.Right(), nil) + case ir.ORETURN: + results := e.curfn.Type().Results().FieldSlice() + for i, v := range n.List().Slice() { + e.assign(ir.AsNode(results[i].Nname), v, "return", n) + } + case ir.OCALLFUNC, ir.OCALLMETH, ir.OCALLINTER, ir.OCLOSE, ir.OCOPY, ir.ODELETE, ir.OPANIC, ir.OPRINT, ir.OPRINTN, ir.ORECOVER: e.call(nil, n, nil) - case OGO, ODEFER: - e.stmts(n.Left.Ninit) - e.call(nil, n.Left, n) + case ir.OGO, ir.ODEFER: + e.stmts(n.Left().Init()) + e.call(nil, n.Left(), n) - case ORETJMP: + case ir.ORETJMP: // TODO(mdempsky): What do? esc.go just ignores it. } } -func (e *Escape) stmts(l Nodes) { +func (e *Escape) stmts(l ir.Nodes) { for _, n := range l.Slice() { e.stmt(n) } } // block is like stmts, but preserves loopDepth. -func (e *Escape) block(l Nodes) { +func (e *Escape) block(l ir.Nodes) { old := e.loopDepth e.stmts(l) e.loopDepth = old @@ -410,123 +447,123 @@ func (e *Escape) block(l Nodes) { // expr models evaluating an expression n and flowing the result into // hole k. -func (e *Escape) expr(k EscHole, n *Node) { +func (e *Escape) expr(k EscHole, n ir.Node) { if n == nil { return } - e.stmts(n.Ninit) + e.stmts(n.Init()) e.exprSkipInit(k, n) } -func (e *Escape) exprSkipInit(k EscHole, n *Node) { +func (e *Escape) exprSkipInit(k EscHole, n ir.Node) { if n == nil { return } lno := setlineno(n) defer func() { - lineno = lno + base.Pos = lno }() uintptrEscapesHack := k.uintptrEscapesHack k.uintptrEscapesHack = false - if uintptrEscapesHack && n.Op == OCONVNOP && n.Left.Type.IsUnsafePtr() { + if uintptrEscapesHack && n.Op() == ir.OCONVNOP && n.Left().Type().IsUnsafePtr() { // nop - } else if k.derefs >= 0 && !n.Type.HasPointers() { + } else if k.derefs >= 0 && !n.Type().HasPointers() { k = e.discardHole() } - switch n.Op { + switch n.Op() { default: - Fatalf("unexpected expr: %v", n) + base.Fatalf("unexpected expr: %v", n) - case OLITERAL, OGETG, OCLOSUREVAR, OTYPE: + case ir.OLITERAL, ir.ONIL, ir.OGETG, ir.OCLOSUREVAR, ir.OTYPE, ir.OMETHEXPR: // nop - case ONAME: - if n.Class() == PFUNC || n.Class() == PEXTERN { + case ir.ONAME: + if n.Class() == ir.PFUNC || n.Class() == ir.PEXTERN { return } e.flow(k, e.oldLoc(n)) - case OPLUS, ONEG, OBITNOT, ONOT: - e.discard(n.Left) - case OADD, OSUB, OOR, OXOR, OMUL, ODIV, OMOD, OLSH, ORSH, OAND, OANDNOT, OEQ, ONE, OLT, OLE, OGT, OGE, OANDAND, OOROR: - e.discard(n.Left) - e.discard(n.Right) - - case OADDR: - e.expr(k.addr(n, "address-of"), n.Left) // "address-of" - case ODEREF: - e.expr(k.deref(n, "indirection"), n.Left) // "indirection" - case ODOT, ODOTMETH, ODOTINTER: - e.expr(k.note(n, "dot"), n.Left) - case ODOTPTR: - e.expr(k.deref(n, "dot of pointer"), n.Left) // "dot of pointer" - case ODOTTYPE, ODOTTYPE2: - e.expr(k.dotType(n.Type, n, "dot"), n.Left) - case OINDEX: - if n.Left.Type.IsArray() { - e.expr(k.note(n, "fixed-array-index-of"), n.Left) + case ir.OPLUS, ir.ONEG, ir.OBITNOT, ir.ONOT: + e.discard(n.Left()) + case ir.OADD, ir.OSUB, ir.OOR, ir.OXOR, ir.OMUL, ir.ODIV, ir.OMOD, ir.OLSH, ir.ORSH, ir.OAND, ir.OANDNOT, ir.OEQ, ir.ONE, ir.OLT, ir.OLE, ir.OGT, ir.OGE, ir.OANDAND, ir.OOROR: + e.discard(n.Left()) + e.discard(n.Right()) + + case ir.OADDR: + e.expr(k.addr(n, "address-of"), n.Left()) // "address-of" + case ir.ODEREF: + e.expr(k.deref(n, "indirection"), n.Left()) // "indirection" + case ir.ODOT, ir.ODOTMETH, ir.ODOTINTER: + e.expr(k.note(n, "dot"), n.Left()) + case ir.ODOTPTR: + e.expr(k.deref(n, "dot of pointer"), n.Left()) // "dot of pointer" + case ir.ODOTTYPE, ir.ODOTTYPE2: + e.expr(k.dotType(n.Type(), n, "dot"), n.Left()) + case ir.OINDEX: + if n.Left().Type().IsArray() { + e.expr(k.note(n, "fixed-array-index-of"), n.Left()) } else { // TODO(mdempsky): Fix why reason text. - e.expr(k.deref(n, "dot of pointer"), n.Left) - } - e.discard(n.Right) - case OINDEXMAP: - e.discard(n.Left) - e.discard(n.Right) - case OSLICE, OSLICEARR, OSLICE3, OSLICE3ARR, OSLICESTR: - e.expr(k.note(n, "slice"), n.Left) + e.expr(k.deref(n, "dot of pointer"), n.Left()) + } + e.discard(n.Right()) + case ir.OINDEXMAP: + e.discard(n.Left()) + e.discard(n.Right()) + case ir.OSLICE, ir.OSLICEARR, ir.OSLICE3, ir.OSLICE3ARR, ir.OSLICESTR: + e.expr(k.note(n, "slice"), n.Left()) low, high, max := n.SliceBounds() e.discard(low) e.discard(high) e.discard(max) - case OCONV, OCONVNOP: - if checkPtr(e.curfn, 2) && n.Type.IsUnsafePtr() && n.Left.Type.IsPtr() { + case ir.OCONV, ir.OCONVNOP: + if checkPtr(e.curfn, 2) && n.Type().IsUnsafePtr() && n.Left().Type().IsPtr() { // When -d=checkptr=2 is enabled, treat // conversions to unsafe.Pointer as an // escaping operation. This allows better // runtime instrumentation, since we can more // easily detect object boundaries on the heap // than the stack. - e.assignHeap(n.Left, "conversion to unsafe.Pointer", n) - } else if n.Type.IsUnsafePtr() && n.Left.Type.IsUintptr() { - e.unsafeValue(k, n.Left) + e.assignHeap(n.Left(), "conversion to unsafe.Pointer", n) + } else if n.Type().IsUnsafePtr() && n.Left().Type().IsUintptr() { + e.unsafeValue(k, n.Left()) } else { - e.expr(k, n.Left) + e.expr(k, n.Left()) } - case OCONVIFACE: - if !n.Left.Type.IsInterface() && !isdirectiface(n.Left.Type) { + case ir.OCONVIFACE: + if !n.Left().Type().IsInterface() && !isdirectiface(n.Left().Type()) { k = e.spill(k, n) } - e.expr(k.note(n, "interface-converted"), n.Left) + e.expr(k.note(n, "interface-converted"), n.Left()) - case ORECV: - e.discard(n.Left) + case ir.ORECV: + e.discard(n.Left()) - case OCALLMETH, OCALLFUNC, OCALLINTER, OLEN, OCAP, OCOMPLEX, OREAL, OIMAG, OAPPEND, OCOPY: + case ir.OCALLMETH, ir.OCALLFUNC, ir.OCALLINTER, ir.OLEN, ir.OCAP, ir.OCOMPLEX, ir.OREAL, ir.OIMAG, ir.OAPPEND, ir.OCOPY: e.call([]EscHole{k}, n, nil) - case ONEW: + case ir.ONEW: e.spill(k, n) - case OMAKESLICE: + case ir.OMAKESLICE: e.spill(k, n) - e.discard(n.Left) - e.discard(n.Right) - case OMAKECHAN: - e.discard(n.Left) - case OMAKEMAP: + e.discard(n.Left()) + e.discard(n.Right()) + case ir.OMAKECHAN: + e.discard(n.Left()) + case ir.OMAKEMAP: e.spill(k, n) - e.discard(n.Left) + e.discard(n.Left()) - case ORECOVER: + case ir.ORECOVER: // nop - case OCALLPART: + case ir.OCALLPART: // Flow the receiver argument to both the closure and // to the receiver parameter. @@ -539,113 +576,113 @@ func (e *Escape) exprSkipInit(k EscHole, n *Node) { // parameters all flow to the heap. // // TODO(mdempsky): Change ks into a callback, so that - // we don't have to create this dummy slice? + // we don't have to create this slice? var ks []EscHole for i := m.Type.NumResults(); i > 0; i-- { ks = append(ks, e.heapHole()) } - paramK := e.tagHole(ks, asNode(m.Type.Nname()), m.Type.Recv()) + paramK := e.tagHole(ks, ir.AsNode(m.Nname), m.Type.Recv()) - e.expr(e.teeHole(paramK, closureK), n.Left) + e.expr(e.teeHole(paramK, closureK), n.Left()) - case OPTRLIT: - e.expr(e.spill(k, n), n.Left) + case ir.OPTRLIT: + e.expr(e.spill(k, n), n.Left()) - case OARRAYLIT: - for _, elt := range n.List.Slice() { - if elt.Op == OKEY { - elt = elt.Right + case ir.OARRAYLIT: + for _, elt := range n.List().Slice() { + if elt.Op() == ir.OKEY { + elt = elt.Right() } e.expr(k.note(n, "array literal element"), elt) } - case OSLICELIT: + case ir.OSLICELIT: k = e.spill(k, n) k.uintptrEscapesHack = uintptrEscapesHack // for ...uintptr parameters - for _, elt := range n.List.Slice() { - if elt.Op == OKEY { - elt = elt.Right + for _, elt := range n.List().Slice() { + if elt.Op() == ir.OKEY { + elt = elt.Right() } e.expr(k.note(n, "slice-literal-element"), elt) } - case OSTRUCTLIT: - for _, elt := range n.List.Slice() { - e.expr(k.note(n, "struct literal element"), elt.Left) + case ir.OSTRUCTLIT: + for _, elt := range n.List().Slice() { + e.expr(k.note(n, "struct literal element"), elt.Left()) } - case OMAPLIT: + case ir.OMAPLIT: e.spill(k, n) // Map keys and values are always stored in the heap. - for _, elt := range n.List.Slice() { - e.assignHeap(elt.Left, "map literal key", n) - e.assignHeap(elt.Right, "map literal value", n) + for _, elt := range n.List().Slice() { + e.assignHeap(elt.Left(), "map literal key", n) + e.assignHeap(elt.Right(), "map literal value", n) } - case OCLOSURE: + case ir.OCLOSURE: k = e.spill(k, n) // Link addresses of captured variables to closure. - for _, v := range n.Func.Closure.Func.Cvars.Slice() { - if v.Op == OXXX { // unnamed out argument; see dcl.go:/^funcargs + for _, v := range n.Func().ClosureVars.Slice() { + if v.Op() == ir.OXXX { // unnamed out argument; see dcl.go:/^funcargs continue } k := k - if !v.Name.Byval() { + if !v.Name().Byval() { k = k.addr(v, "reference") } - e.expr(k.note(n, "captured by a closure"), v.Name.Defn) + e.expr(k.note(n, "captured by a closure"), v.Name().Defn) } - case ORUNES2STR, OBYTES2STR, OSTR2RUNES, OSTR2BYTES, ORUNESTR: + case ir.ORUNES2STR, ir.OBYTES2STR, ir.OSTR2RUNES, ir.OSTR2BYTES, ir.ORUNESTR: e.spill(k, n) - e.discard(n.Left) + e.discard(n.Left()) - case OADDSTR: + case ir.OADDSTR: e.spill(k, n) // Arguments of OADDSTR never escape; // runtime.concatstrings makes sure of that. - e.discards(n.List) + e.discards(n.List()) } } // unsafeValue evaluates a uintptr-typed arithmetic expression looking // for conversions from an unsafe.Pointer. -func (e *Escape) unsafeValue(k EscHole, n *Node) { - if n.Type.Etype != TUINTPTR { - Fatalf("unexpected type %v for %v", n.Type, n) +func (e *Escape) unsafeValue(k EscHole, n ir.Node) { + if n.Type().Etype != types.TUINTPTR { + base.Fatalf("unexpected type %v for %v", n.Type(), n) } - e.stmts(n.Ninit) + e.stmts(n.Init()) - switch n.Op { - case OCONV, OCONVNOP: - if n.Left.Type.IsUnsafePtr() { - e.expr(k, n.Left) + switch n.Op() { + case ir.OCONV, ir.OCONVNOP: + if n.Left().Type().IsUnsafePtr() { + e.expr(k, n.Left()) } else { - e.discard(n.Left) + e.discard(n.Left()) } - case ODOTPTR: + case ir.ODOTPTR: if isReflectHeaderDataField(n) { - e.expr(k.deref(n, "reflect.Header.Data"), n.Left) + e.expr(k.deref(n, "reflect.Header.Data"), n.Left()) } else { - e.discard(n.Left) - } - case OPLUS, ONEG, OBITNOT: - e.unsafeValue(k, n.Left) - case OADD, OSUB, OOR, OXOR, OMUL, ODIV, OMOD, OAND, OANDNOT: - e.unsafeValue(k, n.Left) - e.unsafeValue(k, n.Right) - case OLSH, ORSH: - e.unsafeValue(k, n.Left) + e.discard(n.Left()) + } + case ir.OPLUS, ir.ONEG, ir.OBITNOT: + e.unsafeValue(k, n.Left()) + case ir.OADD, ir.OSUB, ir.OOR, ir.OXOR, ir.OMUL, ir.ODIV, ir.OMOD, ir.OAND, ir.OANDNOT: + e.unsafeValue(k, n.Left()) + e.unsafeValue(k, n.Right()) + case ir.OLSH, ir.ORSH: + e.unsafeValue(k, n.Left()) // RHS need not be uintptr-typed (#32959) and can't meaningfully // flow pointers anyway. - e.discard(n.Right) + e.discard(n.Right()) default: e.exprSkipInit(e.discardHole(), n) } @@ -653,11 +690,11 @@ func (e *Escape) unsafeValue(k EscHole, n *Node) { // discard evaluates an expression n for side-effects, but discards // its value. -func (e *Escape) discard(n *Node) { +func (e *Escape) discard(n ir.Node) { e.expr(e.discardHole(), n) } -func (e *Escape) discards(l Nodes) { +func (e *Escape) discards(l ir.Nodes) { for _, n := range l.Slice() { e.discard(n) } @@ -665,8 +702,8 @@ func (e *Escape) discards(l Nodes) { // addr evaluates an addressable expression n and returns an EscHole // that represents storing into the represented location. -func (e *Escape) addr(n *Node) EscHole { - if n == nil || n.isBlank() { +func (e *Escape) addr(n ir.Node) EscHole { + if n == nil || ir.IsBlank(n) { // Can happen at least in OSELRECV. // TODO(mdempsky): Anywhere else? return e.discardHole() @@ -674,38 +711,38 @@ func (e *Escape) addr(n *Node) EscHole { k := e.heapHole() - switch n.Op { + switch n.Op() { default: - Fatalf("unexpected addr: %v", n) - case ONAME: - if n.Class() == PEXTERN { + base.Fatalf("unexpected addr: %v", n) + case ir.ONAME: + if n.Class() == ir.PEXTERN { break } k = e.oldLoc(n).asHole() - case ODOT: - k = e.addr(n.Left) - case OINDEX: - e.discard(n.Right) - if n.Left.Type.IsArray() { - k = e.addr(n.Left) + case ir.ODOT: + k = e.addr(n.Left()) + case ir.OINDEX: + e.discard(n.Right()) + if n.Left().Type().IsArray() { + k = e.addr(n.Left()) } else { - e.discard(n.Left) + e.discard(n.Left()) } - case ODEREF, ODOTPTR: + case ir.ODEREF, ir.ODOTPTR: e.discard(n) - case OINDEXMAP: - e.discard(n.Left) - e.assignHeap(n.Right, "key of map put", n) + case ir.OINDEXMAP: + e.discard(n.Left()) + e.assignHeap(n.Right(), "key of map put", n) } - if !n.Type.HasPointers() { + if !n.Type().HasPointers() { k = e.discardHole() } return k } -func (e *Escape) addrs(l Nodes) []EscHole { +func (e *Escape) addrs(l ir.Nodes) []EscHole { var ks []EscHole for _, n := range l.Slice() { ks = append(ks, e.addr(n)) @@ -714,15 +751,15 @@ func (e *Escape) addrs(l Nodes) []EscHole { } // assign evaluates the assignment dst = src. -func (e *Escape) assign(dst, src *Node, why string, where *Node) { +func (e *Escape) assign(dst, src ir.Node, why string, where ir.Node) { // Filter out some no-op assignments for escape analysis. ignore := dst != nil && src != nil && isSelfAssign(dst, src) - if ignore && Debug.m != 0 { - Warnl(where.Pos, "%v ignoring self-assignment in %S", funcSym(e.curfn), where) + if ignore && base.Flag.LowerM != 0 { + base.WarnfAt(where.Pos(), "%v ignoring self-assignment in %S", funcSym(e.curfn), where) } k := e.addr(dst) - if dst != nil && dst.Op == ODOTPTR && isReflectHeaderDataField(dst) { + if dst != nil && dst.Op() == ir.ODOTPTR && isReflectHeaderDataField(dst) { e.unsafeValue(e.heapHole().note(where, why), src) } else { if ignore { @@ -732,22 +769,22 @@ func (e *Escape) assign(dst, src *Node, why string, where *Node) { } } -func (e *Escape) assignHeap(src *Node, why string, where *Node) { +func (e *Escape) assignHeap(src ir.Node, why string, where ir.Node) { e.expr(e.heapHole().note(where, why), src) } // call evaluates a call expressions, including builtin calls. ks // should contain the holes representing where the function callee's // results flows; where is the OGO/ODEFER context of the call, if any. -func (e *Escape) call(ks []EscHole, call, where *Node) { - topLevelDefer := where != nil && where.Op == ODEFER && e.loopDepth == 1 +func (e *Escape) call(ks []EscHole, call, where ir.Node) { + topLevelDefer := where != nil && where.Op() == ir.ODEFER && e.loopDepth == 1 if topLevelDefer { // force stack allocation of defer record, unless // open-coded defers are used (see ssa.go) - where.Esc = EscNever + where.SetEsc(EscNever) } - argument := func(k EscHole, arg *Node) { + argument := func(k EscHole, arg ir.Node) { if topLevelDefer { // Top level defers arguments don't escape to // heap, but they do need to last until end of @@ -760,66 +797,66 @@ func (e *Escape) call(ks []EscHole, call, where *Node) { e.expr(k.note(call, "call parameter"), arg) } - switch call.Op { + switch call.Op() { default: - Fatalf("unexpected call op: %v", call.Op) + base.Fatalf("unexpected call op: %v", call.Op()) - case OCALLFUNC, OCALLMETH, OCALLINTER: + case ir.OCALLFUNC, ir.OCALLMETH, ir.OCALLINTER: fixVariadicCall(call) // Pick out the function callee, if statically known. - var fn *Node - switch call.Op { - case OCALLFUNC: - switch v := staticValue(call.Left); { - case v.Op == ONAME && v.Class() == PFUNC: + var fn ir.Node + switch call.Op() { + case ir.OCALLFUNC: + switch v := staticValue(call.Left()); { + case v.Op() == ir.ONAME && v.Class() == ir.PFUNC: fn = v - case v.Op == OCLOSURE: - fn = v.Func.Closure.Func.Nname + case v.Op() == ir.OCLOSURE: + fn = v.Func().Nname } - case OCALLMETH: - fn = asNode(call.Left.Type.FuncType().Nname) + case ir.OCALLMETH: + fn = methodExprName(call.Left()) } - fntype := call.Left.Type + fntype := call.Left().Type() if fn != nil { - fntype = fn.Type + fntype = fn.Type() } if ks != nil && fn != nil && e.inMutualBatch(fn) { - for i, result := range fn.Type.Results().FieldSlice() { - e.expr(ks[i], asNode(result.Nname)) + for i, result := range fn.Type().Results().FieldSlice() { + e.expr(ks[i], ir.AsNode(result.Nname)) } } if r := fntype.Recv(); r != nil { - argument(e.tagHole(ks, fn, r), call.Left.Left) + argument(e.tagHole(ks, fn, r), call.Left().Left()) } else { // Evaluate callee function expression. - argument(e.discardHole(), call.Left) + argument(e.discardHole(), call.Left()) } - args := call.List.Slice() + args := call.List().Slice() for i, param := range fntype.Params().FieldSlice() { argument(e.tagHole(ks, fn, param), args[i]) } - case OAPPEND: - args := call.List.Slice() + case ir.OAPPEND: + args := call.List().Slice() // Appendee slice may flow directly to the result, if // it has enough capacity. Alternatively, a new heap // slice might be allocated, and all slice elements // might flow to heap. appendeeK := ks[0] - if args[0].Type.Elem().HasPointers() { + if args[0].Type().Elem().HasPointers() { appendeeK = e.teeHole(appendeeK, e.heapHole().deref(call, "appendee slice")) } argument(appendeeK, args[0]) if call.IsDDD() { appendedK := e.discardHole() - if args[1].Type.IsSlice() && args[1].Type.Elem().HasPointers() { + if args[1].Type().IsSlice() && args[1].Type().Elem().HasPointers() { appendedK = e.heapHole().deref(call, "appended slice...") } argument(appendedK, args[1]) @@ -829,27 +866,27 @@ func (e *Escape) call(ks []EscHole, call, where *Node) { } } - case OCOPY: - argument(e.discardHole(), call.Left) + case ir.OCOPY: + argument(e.discardHole(), call.Left()) copiedK := e.discardHole() - if call.Right.Type.IsSlice() && call.Right.Type.Elem().HasPointers() { + if call.Right().Type().IsSlice() && call.Right().Type().Elem().HasPointers() { copiedK = e.heapHole().deref(call, "copied slice") } - argument(copiedK, call.Right) + argument(copiedK, call.Right()) - case OPANIC: - argument(e.heapHole(), call.Left) + case ir.OPANIC: + argument(e.heapHole(), call.Left()) - case OCOMPLEX: - argument(e.discardHole(), call.Left) - argument(e.discardHole(), call.Right) - case ODELETE, OPRINT, OPRINTN, ORECOVER: - for _, arg := range call.List.Slice() { + case ir.OCOMPLEX: + argument(e.discardHole(), call.Left()) + argument(e.discardHole(), call.Right()) + case ir.ODELETE, ir.OPRINT, ir.OPRINTN, ir.ORECOVER: + for _, arg := range call.List().Slice() { argument(e.discardHole(), arg) } - case OLEN, OCAP, OREAL, OIMAG, OCLOSE: - argument(e.discardHole(), call.Left) + case ir.OLEN, ir.OCAP, ir.OREAL, ir.OIMAG, ir.OCLOSE: + argument(e.discardHole(), call.Left()) } } @@ -857,14 +894,14 @@ func (e *Escape) call(ks []EscHole, call, where *Node) { // ks should contain the holes representing where the function // callee's results flows. fn is the statically-known callee function, // if any. -func (e *Escape) tagHole(ks []EscHole, fn *Node, param *types.Field) EscHole { +func (e *Escape) tagHole(ks []EscHole, fn ir.Node, param *types.Field) EscHole { // If this is a dynamic call, we can't rely on param.Note. if fn == nil { return e.heapHole() } if e.inMutualBatch(fn) { - return e.addr(asNode(param.Nname)) + return e.addr(ir.AsNode(param.Nname)) } // Call to previously tagged function. @@ -898,10 +935,10 @@ func (e *Escape) tagHole(ks []EscHole, fn *Node, param *types.Field) EscHole { // fn has not yet been analyzed, so its parameters and results // should be incorporated directly into the flow graph instead of // relying on its escape analysis tagging. -func (e *Escape) inMutualBatch(fn *Node) bool { - if fn.Name.Defn != nil && fn.Name.Defn.Esc < EscFuncTagged { - if fn.Name.Defn.Esc == EscFuncUnknown { - Fatalf("graph inconsistency") +func (e *Escape) inMutualBatch(fn ir.Node) bool { + if fn.Name().Defn != nil && fn.Name().Defn.Esc() < EscFuncTagged { + if fn.Name().Defn.Esc() == EscFuncUnknown { + base.Fatalf("graph inconsistency") } return true } @@ -923,15 +960,15 @@ type EscHole struct { type EscNote struct { next *EscNote - where *Node + where ir.Node why string } -func (k EscHole) note(where *Node, why string) EscHole { +func (k EscHole) note(where ir.Node, why string) EscHole { if where == nil || why == "" { - Fatalf("note: missing where/why") + base.Fatalf("note: missing where/why") } - if Debug.m >= 2 || logopt.Enabled() { + if base.Flag.LowerM >= 2 || logopt.Enabled() { k.notes = &EscNote{ next: k.notes, where: where, @@ -944,15 +981,15 @@ func (k EscHole) note(where *Node, why string) EscHole { func (k EscHole) shift(delta int) EscHole { k.derefs += delta if k.derefs < -1 { - Fatalf("derefs underflow: %v", k.derefs) + base.Fatalf("derefs underflow: %v", k.derefs) } return k } -func (k EscHole) deref(where *Node, why string) EscHole { return k.shift(1).note(where, why) } -func (k EscHole) addr(where *Node, why string) EscHole { return k.shift(-1).note(where, why) } +func (k EscHole) deref(where ir.Node, why string) EscHole { return k.shift(1).note(where, why) } +func (k EscHole) addr(where ir.Node, why string) EscHole { return k.shift(-1).note(where, why) } -func (k EscHole) dotType(t *types.Type, where *Node, why string) EscHole { +func (k EscHole) dotType(t *types.Type, where ir.Node, why string) EscHole { if !t.IsInterface() && !isdirectiface(t) { k = k.shift(1) } @@ -981,7 +1018,7 @@ func (e *Escape) teeHole(ks ...EscHole) EscHole { // *ltmp" and "l2 = ltmp" and return "ltmp = &_" // instead. if k.derefs < 0 { - Fatalf("teeHole: negative derefs") + base.Fatalf("teeHole: negative derefs") } e.flow(k, loc) @@ -989,7 +1026,7 @@ func (e *Escape) teeHole(ks ...EscHole) EscHole { return loc.asHole() } -func (e *Escape) dcl(n *Node) EscHole { +func (e *Escape) dcl(n ir.Node) EscHole { loc := e.oldLoc(n) loc.loopDepth = e.loopDepth return loc.asHole() @@ -998,7 +1035,7 @@ func (e *Escape) dcl(n *Node) EscHole { // spill allocates a new location associated with expression n, flows // its address to k, and returns a hole that flows values to it. It's // intended for use with most expressions that allocate storage. -func (e *Escape) spill(k EscHole, n *Node) EscHole { +func (e *Escape) spill(k EscHole, n ir.Node) EscHole { loc := e.newLoc(n, true) e.flow(k.addr(n, "spill"), loc) return loc.asHole() @@ -1015,23 +1052,23 @@ func (e *Escape) later(k EscHole) EscHole { // canonicalNode returns the canonical *Node that n logically // represents. -func canonicalNode(n *Node) *Node { - if n != nil && n.Op == ONAME && n.Name.IsClosureVar() { - n = n.Name.Defn - if n.Name.IsClosureVar() { - Fatalf("still closure var") +func canonicalNode(n ir.Node) ir.Node { + if n != nil && n.Op() == ir.ONAME && n.Name().IsClosureVar() { + n = n.Name().Defn + if n.Name().IsClosureVar() { + base.Fatalf("still closure var") } } return n } -func (e *Escape) newLoc(n *Node, transient bool) *EscLocation { +func (e *Escape) newLoc(n ir.Node, transient bool) *EscLocation { if e.curfn == nil { - Fatalf("e.curfn isn't set") + base.Fatalf("e.curfn isn't set") } - if n != nil && n.Type != nil && n.Type.NotInHeap() { - yyerrorl(n.Pos, "%v is incomplete (or unallocatable); stack allocation disallowed", n.Type) + if n != nil && n.Type() != nil && n.Type().NotInHeap() { + base.ErrorfAt(n.Pos(), "%v is incomplete (or unallocatable); stack allocation disallowed", n.Type()) } n = canonicalNode(n) @@ -1043,12 +1080,12 @@ func (e *Escape) newLoc(n *Node, transient bool) *EscLocation { } e.allLocs = append(e.allLocs, loc) if n != nil { - if n.Op == ONAME && n.Name.Curfn != e.curfn { - Fatalf("curfn mismatch: %v != %v", n.Name.Curfn, e.curfn) + if n.Op() == ir.ONAME && n.Name().Curfn != e.curfn { + base.Fatalf("curfn mismatch: %v != %v", n.Name().Curfn, e.curfn) } if n.HasOpt() { - Fatalf("%v already has a location", n) + base.Fatalf("%v already has a location", n) } n.SetOpt(loc) @@ -1059,7 +1096,7 @@ func (e *Escape) newLoc(n *Node, transient bool) *EscLocation { return loc } -func (e *Escape) oldLoc(n *Node) *EscLocation { +func (e *Escape) oldLoc(n ir.Node) *EscLocation { n = canonicalNode(n) return n.Opt().(*EscLocation) } @@ -1077,14 +1114,14 @@ func (e *Escape) flow(k EscHole, src *EscLocation) { return } if dst.escapes && k.derefs < 0 { // dst = &src - if Debug.m >= 2 || logopt.Enabled() { - pos := linestr(src.n.Pos) - if Debug.m >= 2 { + if base.Flag.LowerM >= 2 || logopt.Enabled() { + pos := base.FmtPos(src.n.Pos()) + if base.Flag.LowerM >= 2 { fmt.Printf("%s: %v escapes to heap:\n", pos, src.n) } explanation := e.explainFlow(pos, dst, src, k.derefs, k.notes, []*logopt.LoggedOpt{}) if logopt.Enabled() { - logopt.LogOpt(src.n.Pos, "escapes", "escape", e.curfn.funcname(), fmt.Sprintf("%v escapes to heap", src.n), explanation) + logopt.LogOpt(src.n.Pos(), "escapes", "escape", ir.FuncName(e.curfn), fmt.Sprintf("%v escapes to heap", src.n), explanation) } } @@ -1152,16 +1189,16 @@ func (e *Escape) walkOne(root *EscLocation, walkgen uint32, enqueue func(*EscLoc l := todo[len(todo)-1] todo = todo[:len(todo)-1] - base := l.derefs + derefs := l.derefs // If l.derefs < 0, then l's address flows to root. - addressOf := base < 0 + addressOf := derefs < 0 if addressOf { // For a flow path like "root = &l; l = x", // l's address flows to root, but x's does // not. We recognize this by lower bounding - // base at 0. - base = 0 + // derefs at 0. + derefs = 0 // If l's address flows to a non-transient // location, then l can't be transiently @@ -1178,31 +1215,31 @@ func (e *Escape) walkOne(root *EscLocation, walkgen uint32, enqueue func(*EscLoc // corresponding result parameter, then record // that value flow for tagging the function // later. - if l.isName(PPARAM) { - if (logopt.Enabled() || Debug.m >= 2) && !l.escapes { - if Debug.m >= 2 { - fmt.Printf("%s: parameter %v leaks to %s with derefs=%d:\n", linestr(l.n.Pos), l.n, e.explainLoc(root), base) + if l.isName(ir.PPARAM) { + if (logopt.Enabled() || base.Flag.LowerM >= 2) && !l.escapes { + if base.Flag.LowerM >= 2 { + fmt.Printf("%s: parameter %v leaks to %s with derefs=%d:\n", base.FmtPos(l.n.Pos()), l.n, e.explainLoc(root), derefs) } explanation := e.explainPath(root, l) if logopt.Enabled() { - logopt.LogOpt(l.n.Pos, "leak", "escape", e.curfn.funcname(), - fmt.Sprintf("parameter %v leaks to %s with derefs=%d", l.n, e.explainLoc(root), base), explanation) + logopt.LogOpt(l.n.Pos(), "leak", "escape", ir.FuncName(e.curfn), + fmt.Sprintf("parameter %v leaks to %s with derefs=%d", l.n, e.explainLoc(root), derefs), explanation) } } - l.leakTo(root, base) + l.leakTo(root, derefs) } // If l's address flows somewhere that // outlives it, then l needs to be heap // allocated. if addressOf && !l.escapes { - if logopt.Enabled() || Debug.m >= 2 { - if Debug.m >= 2 { - fmt.Printf("%s: %v escapes to heap:\n", linestr(l.n.Pos), l.n) + if logopt.Enabled() || base.Flag.LowerM >= 2 { + if base.Flag.LowerM >= 2 { + fmt.Printf("%s: %v escapes to heap:\n", base.FmtPos(l.n.Pos()), l.n) } explanation := e.explainPath(root, l) if logopt.Enabled() { - logopt.LogOpt(l.n.Pos, "escape", "escape", e.curfn.funcname(), fmt.Sprintf("%v escapes to heap", l.n), explanation) + logopt.LogOpt(l.n.Pos(), "escape", "escape", ir.FuncName(e.curfn), fmt.Sprintf("%v escapes to heap", l.n), explanation) } } l.escapes = true @@ -1215,10 +1252,10 @@ func (e *Escape) walkOne(root *EscLocation, walkgen uint32, enqueue func(*EscLoc if edge.src.escapes { continue } - derefs := base + edge.derefs - if edge.src.walkgen != walkgen || edge.src.derefs > derefs { + d := derefs + edge.derefs + if edge.src.walkgen != walkgen || edge.src.derefs > d { edge.src.walkgen = walkgen - edge.src.derefs = derefs + edge.src.derefs = d edge.src.dst = l edge.src.dstEdgeIdx = i todo = append(todo, edge.src) @@ -1230,12 +1267,12 @@ func (e *Escape) walkOne(root *EscLocation, walkgen uint32, enqueue func(*EscLoc // explainPath prints an explanation of how src flows to the walk root. func (e *Escape) explainPath(root, src *EscLocation) []*logopt.LoggedOpt { visited := make(map[*EscLocation]bool) - pos := linestr(src.n.Pos) + pos := base.FmtPos(src.n.Pos()) var explanation []*logopt.LoggedOpt for { // Prevent infinite loop. if visited[src] { - if Debug.m >= 2 { + if base.Flag.LowerM >= 2 { fmt.Printf("%s: warning: truncated explanation due to assignment cycle; see golang.org/issue/35518\n", pos) } break @@ -1244,7 +1281,7 @@ func (e *Escape) explainPath(root, src *EscLocation) []*logopt.LoggedOpt { dst := src.dst edge := &dst.edges[src.dstEdgeIdx] if edge.src != src { - Fatalf("path inconsistency: %v != %v", edge.src, src) + base.Fatalf("path inconsistency: %v != %v", edge.src, src) } explanation = e.explainFlow(pos, dst, src, edge.derefs, edge.notes, explanation) @@ -1263,7 +1300,7 @@ func (e *Escape) explainFlow(pos string, dst, srcloc *EscLocation, derefs int, n if derefs >= 0 { ops = strings.Repeat("*", derefs) } - print := Debug.m >= 2 + print := base.Flag.LowerM >= 2 flow := fmt.Sprintf(" flow: %s = %s%v:", e.explainLoc(dst), ops, e.explainLoc(srcloc)) if print { @@ -1272,19 +1309,19 @@ func (e *Escape) explainFlow(pos string, dst, srcloc *EscLocation, derefs int, n if logopt.Enabled() { var epos src.XPos if notes != nil { - epos = notes.where.Pos + epos = notes.where.Pos() } else if srcloc != nil && srcloc.n != nil { - epos = srcloc.n.Pos + epos = srcloc.n.Pos() } - explanation = append(explanation, logopt.NewLoggedOpt(epos, "escflow", "escape", e.curfn.funcname(), flow)) + explanation = append(explanation, logopt.NewLoggedOpt(epos, "escflow", "escape", ir.FuncName(e.curfn), flow)) } for note := notes; note != nil; note = note.next { if print { - fmt.Printf("%s: from %v (%v) at %s\n", pos, note.where, note.why, linestr(note.where.Pos)) + fmt.Printf("%s: from %v (%v) at %s\n", pos, note.where, note.why, base.FmtPos(note.where.Pos())) } if logopt.Enabled() { - explanation = append(explanation, logopt.NewLoggedOpt(note.where.Pos, "escflow", "escape", e.curfn.funcname(), + explanation = append(explanation, logopt.NewLoggedOpt(note.where.Pos(), "escflow", "escape", ir.FuncName(e.curfn), fmt.Sprintf(" from %v (%v)", note.where, note.why))) } } @@ -1299,7 +1336,7 @@ func (e *Escape) explainLoc(l *EscLocation) string { // TODO(mdempsky): Omit entirely. return "{temp}" } - if l.n.Op == ONAME { + if l.n.Op() == ir.ONAME { return fmt.Sprintf("%v", l.n) } return fmt.Sprintf("{storage for %v}", l.n) @@ -1316,14 +1353,14 @@ func (e *Escape) outlives(l, other *EscLocation) bool { // We don't know what callers do with returned values, so // pessimistically we need to assume they flow to the heap and // outlive everything too. - if l.isName(PPARAMOUT) { + if l.isName(ir.PPARAMOUT) { // Exception: Directly called closures can return // locations allocated outside of them without forcing // them to the heap. For example: // // var u int // okay to stack allocate // *(func() *int { return &u }()) = 42 - if containsClosure(other.curfn, l.curfn) && l.curfn.Func.Closure.Func.Top&ctxCallee != 0 { + if containsClosure(other.curfn, l.curfn) && l.curfn.Func().ClosureCalled { return false } @@ -1357,9 +1394,9 @@ func (e *Escape) outlives(l, other *EscLocation) bool { } // containsClosure reports whether c is a closure contained within f. -func containsClosure(f, c *Node) bool { - if f.Op != ODCLFUNC || c.Op != ODCLFUNC { - Fatalf("bad containsClosure: %v, %v", f, c) +func containsClosure(f, c ir.Node) bool { + if f.Op() != ir.ODCLFUNC || c.Op() != ir.ODCLFUNC { + base.Fatalf("bad containsClosure: %v, %v", f, c) } // Common case. @@ -1369,8 +1406,8 @@ func containsClosure(f, c *Node) bool { // Closures within function Foo are named like "Foo.funcN..." // TODO(mdempsky): Better way to recognize this. - fn := f.Func.Nname.Sym.Name - cn := c.Func.Nname.Sym.Name + fn := f.Func().Nname.Sym().Name + cn := c.Func().Nname.Sym().Name return len(cn) > len(fn) && cn[:len(fn)] == fn && cn[len(fn)] == '.' } @@ -1378,9 +1415,9 @@ func containsClosure(f, c *Node) bool { func (l *EscLocation) leakTo(sink *EscLocation, derefs int) { // If sink is a result parameter and we can fit return bits // into the escape analysis tag, then record a return leak. - if sink.isName(PPARAMOUT) && sink.curfn == l.curfn { + if sink.isName(ir.PPARAMOUT) && sink.curfn == l.curfn { // TODO(mdempsky): Eliminate dependency on Vargen here. - ri := int(sink.n.Name.Vargen) - 1 + ri := int(sink.n.Name().Vargen) - 1 if ri < numEscResults { // Leak to result parameter. l.paramEsc.AddResult(ri, derefs) @@ -1392,14 +1429,14 @@ func (l *EscLocation) leakTo(sink *EscLocation, derefs int) { l.paramEsc.AddHeap(derefs) } -func (e *Escape) finish(fns []*Node) { +func (e *Escape) finish(fns []ir.Node) { // Record parameter tags for package export data. for _, fn := range fns { - fn.Esc = EscFuncTagged + fn.SetEsc(EscFuncTagged) narg := 0 for _, fs := range &types.RecvsParams { - for _, f := range fs(fn.Type).Fields().Slice() { + for _, f := range fs(fn.Type()).Fields().Slice() { narg++ f.Note = e.paramTag(fn, narg, f) } @@ -1416,21 +1453,21 @@ func (e *Escape) finish(fns []*Node) { // Update n.Esc based on escape analysis results. if loc.escapes { - if n.Op != ONAME { - if Debug.m != 0 { - Warnl(n.Pos, "%S escapes to heap", n) + if n.Op() != ir.ONAME { + if base.Flag.LowerM != 0 { + base.WarnfAt(n.Pos(), "%S escapes to heap", n) } if logopt.Enabled() { - logopt.LogOpt(n.Pos, "escape", "escape", e.curfn.funcname()) + logopt.LogOpt(n.Pos(), "escape", "escape", ir.FuncName(e.curfn)) } } - n.Esc = EscHeap + n.SetEsc(EscHeap) addrescapes(n) } else { - if Debug.m != 0 && n.Op != ONAME { - Warnl(n.Pos, "%S does not escape", n) + if base.Flag.LowerM != 0 && n.Op() != ir.ONAME { + base.WarnfAt(n.Pos(), "%S does not escape", n) } - n.Esc = EscNone + n.SetEsc(EscNone) if loc.transient { n.SetTransient(true) } @@ -1438,8 +1475,8 @@ func (e *Escape) finish(fns []*Node) { } } -func (l *EscLocation) isName(c Class) bool { - return l.n != nil && l.n.Op == ONAME && l.n.Class() == c +func (l *EscLocation) isName(c ir.Class) bool { + return l.n != nil && l.n.Op() == ir.ONAME && l.n.Class() == c } const numEscResults = 7 @@ -1481,7 +1518,7 @@ func (l *EscLeaks) add(i, derefs int) { func (l *EscLeaks) set(i, derefs int) { v := derefs + 1 if v < 0 { - Fatalf("invalid derefs count: %v", derefs) + base.Fatalf("invalid derefs count: %v", derefs) } if v > math.MaxUint8 { v = math.MaxUint8 @@ -1536,3 +1573,466 @@ func ParseLeaks(s string) EscLeaks { copy(l[:], s[4:]) return l } + +func escapes(all []ir.Node) { + visitBottomUp(all, escapeFuncs) +} + +const ( + EscFuncUnknown = 0 + iota + EscFuncPlanned + EscFuncStarted + EscFuncTagged +) + +func min8(a, b int8) int8 { + if a < b { + return a + } + return b +} + +func max8(a, b int8) int8 { + if a > b { + return a + } + return b +} + +const ( + EscUnknown = iota + EscNone // Does not escape to heap, result, or parameters. + EscHeap // Reachable from the heap + EscNever // By construction will not escape. +) + +// funcSym returns fn.Func.Nname.Sym if no nils are encountered along the way. +func funcSym(fn ir.Node) *types.Sym { + if fn == nil || fn.Func().Nname == nil { + return nil + } + return fn.Func().Nname.Sym() +} + +// 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. +var ( + looping = ir.Nod(ir.OXXX, nil, nil) + nonlooping = ir.Nod(ir.OXXX, nil, nil) +) + +func isSliceSelfAssign(dst, src ir.Node) bool { + // Detect 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. + // Here we 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. + + // dst is ONAME dereference. + if dst.Op() != ir.ODEREF && dst.Op() != ir.ODOTPTR || dst.Left().Op() != ir.ONAME { + return false + } + // src is a slice operation. + switch src.Op() { + case ir.OSLICE, ir.OSLICE3, ir.OSLICESTR: + // OK. + case ir.OSLICEARR, ir.OSLICE3ARR: + // Since arrays are embedded into containing object, + // slice of non-pointer array will 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. + // + // Pointer to an array is OK since it's not stored inside b directly. + // For slicing an array (not pointer to array), there is an implicit OADDR. + // We check that to determine non-pointer array slicing. + if src.Left().Op() == ir.OADDR { + return false + } + default: + return false + } + // slice is applied to ONAME dereference. + if src.Left().Op() != ir.ODEREF && src.Left().Op() != ir.ODOTPTR || src.Left().Left().Op() != ir.ONAME { + return false + } + // dst and src reference the same base ONAME. + return dst.Left() == src.Left().Left() +} + +// isSelfAssign reports whether assignment from src to dst can +// be ignored by the escape analysis as it's effectively a self-assignment. +func isSelfAssign(dst, src ir.Node) bool { + if isSliceSelfAssign(dst, src) { + return true + } + + // Detect trivial assignments that assign back to the same object. + // + // It covers these cases: + // val.x = val.y + // val.x[i] = val.y[j] + // val.x1.x2 = val.x1.y2 + // ... etc + // + // These assignments do not change assigned object lifetime. + + if dst == nil || src == nil || dst.Op() != src.Op() { + return false + } + + switch dst.Op() { + case ir.ODOT, ir.ODOTPTR: + // Safe trailing accessors that are permitted to differ. + case ir.OINDEX: + if mayAffectMemory(dst.Right()) || mayAffectMemory(src.Right()) { + return false + } + default: + return false + } + + // The expression prefix must be both "safe" and identical. + return samesafeexpr(dst.Left(), src.Left()) +} + +// mayAffectMemory reports whether evaluation of n may affect the program's +// memory state. If the expression can't affect memory state, then it can be +// safely ignored by the escape analysis. +func mayAffectMemory(n ir.Node) bool { + // We may want to use a list of "memory safe" ops instead of generally + // "side-effect free", which would include all calls and other ops that can + // allocate or change global state. For now, it's safer to start with the latter. + // + // We're ignoring things like division by zero, index out of range, + // and nil pointer dereference here. + switch n.Op() { + case ir.ONAME, ir.OCLOSUREVAR, ir.OLITERAL, ir.ONIL: + return false + + // Left+Right group. + case ir.OINDEX, ir.OADD, ir.OSUB, ir.OOR, ir.OXOR, ir.OMUL, ir.OLSH, ir.ORSH, ir.OAND, ir.OANDNOT, ir.ODIV, ir.OMOD: + return mayAffectMemory(n.Left()) || mayAffectMemory(n.Right()) + + // Left group. + case ir.ODOT, ir.ODOTPTR, ir.ODEREF, ir.OCONVNOP, ir.OCONV, ir.OLEN, ir.OCAP, + ir.ONOT, ir.OBITNOT, ir.OPLUS, ir.ONEG, ir.OALIGNOF, ir.OOFFSETOF, ir.OSIZEOF: + return mayAffectMemory(n.Left()) + + default: + return true + } +} + +// heapAllocReason returns the reason the given Node must be heap +// allocated, or the empty string if it doesn't. +func heapAllocReason(n ir.Node) string { + if n.Type() == nil { + return "" + } + + // Parameters are always passed via the stack. + if n.Op() == ir.ONAME && (n.Class() == ir.PPARAM || n.Class() == ir.PPARAMOUT) { + return "" + } + + if n.Type().Width > maxStackVarSize { + return "too large for stack" + } + + if (n.Op() == ir.ONEW || n.Op() == ir.OPTRLIT) && n.Type().Elem().Width >= maxImplicitStackVarSize { + return "too large for stack" + } + + if n.Op() == ir.OCLOSURE && closureType(n).Size() >= maxImplicitStackVarSize { + return "too large for stack" + } + if n.Op() == ir.OCALLPART && partialCallType(n).Size() >= maxImplicitStackVarSize { + return "too large for stack" + } + + if n.Op() == ir.OMAKESLICE { + r := n.Right() + if r == nil { + r = n.Left() + } + if !smallintconst(r) { + return "non-constant size" + } + if t := n.Type(); t.Elem().Width != 0 && r.Int64Val() >= maxImplicitStackVarSize/t.Elem().Width { + return "too large for stack" + } + } + + return "" +} + +// addrescapes tags node n as having had its address taken +// by "increasing" the "value" of n.Esc to EscHeap. +// Storage is allocated as necessary to allow the address +// to be taken. +func addrescapes(n ir.Node) { + switch n.Op() { + default: + // Unexpected Op, probably due to a previous type error. Ignore. + + case ir.ODEREF, ir.ODOTPTR: + // Nothing to do. + + case ir.ONAME: + if n == nodfp { + break + } + + // if this is a tmpname (PAUTO), it was tagged by tmpname as not escaping. + // on PPARAM it means something different. + if n.Class() == ir.PAUTO && n.Esc() == EscNever { + break + } + + // If a closure reference escapes, mark the outer variable as escaping. + if n.Name().IsClosureVar() { + addrescapes(n.Name().Defn) + break + } + + if n.Class() != ir.PPARAM && n.Class() != ir.PPARAMOUT && n.Class() != ir.PAUTO { + break + } + + // This is a plain parameter or local variable that needs to move to the heap, + // but possibly for the function outside the one we're compiling. + // That is, if we have: + // + // func f(x int) { + // func() { + // global = &x + // } + // } + // + // then we're analyzing the inner closure but we need to move x to the + // heap in f, not in the inner closure. Flip over to f before calling moveToHeap. + oldfn := Curfn + Curfn = n.Name().Curfn + if Curfn.Op() == ir.OCLOSURE { + Curfn = Curfn.Func().Decl + panic("can't happen") + } + ln := base.Pos + base.Pos = Curfn.Pos() + moveToHeap(n) + Curfn = oldfn + base.Pos = ln + + // ODOTPTR has already been introduced, + // so these are the non-pointer ODOT and OINDEX. + // In &x[0], if x is a slice, then x does not + // escape--the pointer inside x does, but that + // is always a heap pointer anyway. + case ir.ODOT, ir.OINDEX, ir.OPAREN, ir.OCONVNOP: + if !n.Left().Type().IsSlice() { + addrescapes(n.Left()) + } + } +} + +// moveToHeap records the parameter or local variable n as moved to the heap. +func moveToHeap(n ir.Node) { + if base.Flag.LowerR != 0 { + ir.Dump("MOVE", n) + } + if base.Flag.CompilingRuntime { + base.Errorf("%v escapes to heap, not allowed in runtime", n) + } + if n.Class() == ir.PAUTOHEAP { + ir.Dump("n", n) + base.Fatalf("double move to heap") + } + + // Allocate a local stack variable to hold the pointer to the heap copy. + // temp will add it to the function declaration list automatically. + heapaddr := temp(types.NewPtr(n.Type())) + heapaddr.SetSym(lookup("&" + n.Sym().Name)) + heapaddr.Orig().SetSym(heapaddr.Sym()) + heapaddr.SetPos(n.Pos()) + + // Unset AutoTemp to persist the &foo variable name through SSA to + // liveness analysis. + // TODO(mdempsky/drchase): Cleaner solution? + heapaddr.Name().SetAutoTemp(false) + + // Parameters have a local stack copy used at function start/end + // in addition to the copy in the heap that may live longer than + // the function. + if n.Class() == ir.PPARAM || n.Class() == ir.PPARAMOUT { + if n.Offset() == types.BADWIDTH { + base.Fatalf("addrescapes before param assignment") + } + + // We rewrite n below to be a heap variable (indirection of heapaddr). + // Preserve a copy so we can still write code referring to the original, + // and substitute that copy into the function declaration list + // so that analyses of the local (on-stack) variables use it. + stackcopy := NewName(n.Sym()) + stackcopy.SetType(n.Type()) + stackcopy.SetOffset(n.Offset()) + stackcopy.SetClass(n.Class()) + stackcopy.Name().Param.Heapaddr = heapaddr + if n.Class() == ir.PPARAMOUT { + // Make sure the pointer to the heap copy is kept live throughout the function. + // The function could panic at any point, and then a defer could recover. + // Thus, we need the pointer to the heap copy always available so the + // post-deferreturn code can copy the return value back to the stack. + // See issue 16095. + heapaddr.Name().SetIsOutputParamHeapAddr(true) + } + n.Name().Param.Stackcopy = stackcopy + + // Substitute the stackcopy into the function variable list so that + // liveness and other analyses use the underlying stack slot + // and not the now-pseudo-variable n. + found := false + for i, d := range Curfn.Func().Dcl { + if d == n { + Curfn.Func().Dcl[i] = stackcopy + found = true + break + } + // Parameters are before locals, so can stop early. + // This limits the search even in functions with many local variables. + if d.Class() == ir.PAUTO { + break + } + } + if !found { + base.Fatalf("cannot find %v in local variable list", n) + } + Curfn.Func().Dcl = append(Curfn.Func().Dcl, n) + } + + // Modify n in place so that uses of n now mean indirection of the heapaddr. + n.SetClass(ir.PAUTOHEAP) + n.SetOffset(0) + n.Name().Param.Heapaddr = heapaddr + n.SetEsc(EscHeap) + if base.Flag.LowerM != 0 { + base.WarnfAt(n.Pos(), "moved to heap: %v", n) + } +} + +// This special tag is applied to uintptr variables +// that we believe may hold unsafe.Pointers for +// calls into assembly functions. +const unsafeUintptrTag = "unsafe-uintptr" + +// This special tag is applied to uintptr parameters of functions +// marked go:uintptrescapes. +const uintptrEscapesTag = "uintptr-escapes" + +func (e *Escape) paramTag(fn ir.Node, narg int, f *types.Field) string { + name := func() string { + if f.Sym != nil { + return f.Sym.Name + } + return fmt.Sprintf("arg#%d", narg) + } + + if fn.Body().Len() == 0 { + // Assume that uintptr arguments must be held live across the call. + // This is most important for syscall.Syscall. + // See golang.org/issue/13372. + // This really doesn't have much to do with escape analysis per se, + // but we are reusing the ability to annotate an individual function + // argument and pass those annotations along to importing code. + if f.Type.IsUintptr() { + if base.Flag.LowerM != 0 { + base.WarnfAt(f.Pos, "assuming %v is unsafe uintptr", name()) + } + return unsafeUintptrTag + } + + if !f.Type.HasPointers() { // don't bother tagging for scalars + return "" + } + + var esc EscLeaks + + // External functions are assumed unsafe, unless + // //go:noescape is given before the declaration. + if fn.Func().Pragma&ir.Noescape != 0 { + if base.Flag.LowerM != 0 && f.Sym != nil { + base.WarnfAt(f.Pos, "%v does not escape", name()) + } + } else { + if base.Flag.LowerM != 0 && f.Sym != nil { + base.WarnfAt(f.Pos, "leaking param: %v", name()) + } + esc.AddHeap(0) + } + + return esc.Encode() + } + + if fn.Func().Pragma&ir.UintptrEscapes != 0 { + if f.Type.IsUintptr() { + if base.Flag.LowerM != 0 { + base.WarnfAt(f.Pos, "marking %v as escaping uintptr", name()) + } + return uintptrEscapesTag + } + if f.IsDDD() && f.Type.Elem().IsUintptr() { + // final argument is ...uintptr. + if base.Flag.LowerM != 0 { + base.WarnfAt(f.Pos, "marking %v as escaping ...uintptr", name()) + } + return uintptrEscapesTag + } + } + + if !f.Type.HasPointers() { // don't bother tagging for scalars + return "" + } + + // Unnamed parameters are unused and therefore do not escape. + if f.Sym == nil || f.Sym.IsBlank() { + var esc EscLeaks + return esc.Encode() + } + + n := ir.AsNode(f.Nname) + loc := e.oldLoc(n) + esc := loc.paramEsc + esc.Optimize() + + if base.Flag.LowerM != 0 && !loc.escapes { + if esc.Empty() { + base.WarnfAt(f.Pos, "%v does not escape", name()) + } + if x := esc.Heap(); x >= 0 { + if x == 0 { + base.WarnfAt(f.Pos, "leaking param: %v", name()) + } else { + // TODO(mdempsky): Mention level=x like below? + base.WarnfAt(f.Pos, "leaking param content: %v", name()) + } + } + for i := 0; i < numEscResults; i++ { + if x := esc.Result(i); x >= 0 { + res := fn.Type().Results().Field(i).Sym + base.WarnfAt(f.Pos, "leaking param: %v to result %v level=%d", name(), res, x) + } + } + } + + return esc.Encode() +} |