aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/compile/internal/escape/escape.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd/compile/internal/escape/escape.go')
-rw-r--r--src/cmd/compile/internal/escape/escape.go1772
1 files changed, 47 insertions, 1725 deletions
diff --git a/src/cmd/compile/internal/escape/escape.go b/src/cmd/compile/internal/escape/escape.go
index cd56f07b61..61e0121a40 100644
--- a/src/cmd/compile/internal/escape/escape.go
+++ b/src/cmd/compile/internal/escape/escape.go
@@ -6,15 +6,11 @@ package escape
import (
"fmt"
- "math"
- "strings"
"cmd/compile/internal/base"
"cmd/compile/internal/ir"
"cmd/compile/internal/logopt"
- "cmd/compile/internal/typecheck"
"cmd/compile/internal/types"
- "cmd/internal/src"
)
// Escape analysis.
@@ -118,90 +114,8 @@ type escape struct {
loopDepth int
}
-// An location represents an abstract location that stores a Go
-// variable.
-type location struct {
- n ir.Node // represented variable or expression, if any
- curfn *ir.Func // enclosing function
- edges []edge // incoming edges
- loopDepth int // loopDepth at declaration
-
- // resultIndex records the tuple index (starting at 1) for
- // PPARAMOUT variables within their function's result type.
- // For non-PPARAMOUT variables it's 0.
- resultIndex int
-
- // derefs and walkgen are used during walkOne to track the
- // minimal dereferences from the walk root.
- derefs int // >= -1
- walkgen uint32
-
- // dst and dstEdgeindex track the next immediate assignment
- // destination location during walkone, along with the index
- // of the edge pointing back to this location.
- dst *location
- dstEdgeIdx int
-
- // queued is used by walkAll to track whether this location is
- // in the walk queue.
- queued bool
-
- // escapes reports whether the represented variable's address
- // escapes; that is, whether the variable must be heap
- // allocated.
- escapes bool
-
- // transient reports whether the represented expression's
- // address does not outlive the statement; that is, whether
- // its storage can be immediately reused.
- transient bool
-
- // paramEsc records the represented parameter's leak set.
- paramEsc leaks
-
- captured bool // has a closure captured this variable?
- reassigned bool // has this variable been reassigned?
- addrtaken bool // has this variable's address been taken?
-}
-
-// An edge represents an assignment edge between two Go variables.
-type edge struct {
- src *location
- derefs int // >= -1
- notes *note
-}
-
-// Fmt is called from node printing to print information about escape analysis results.
-func Fmt(n ir.Node) string {
- text := ""
- switch n.Esc() {
- case ir.EscUnknown:
- break
-
- case ir.EscHeap:
- text = "esc(h)"
-
- case ir.EscNone:
- text = "esc(no)"
-
- case ir.EscNever:
- text = "esc(N)"
-
- default:
- text = fmt.Sprintf("esc(%d)", n.Esc())
- }
-
- if n.Op() == ir.ONAME {
- n := n.(*ir.Name)
- if loc, ok := n.Opt.(*location); ok && loc.loopDepth != 0 {
- if text != "" {
- text += " "
- }
- text += fmt.Sprintf("ld(%d)", loc.loopDepth)
- }
- }
-
- return text
+func Funcs(all []ir.Node) {
+ ir.VisitFuncsBottomUp(all, Batch)
}
// Batch performs escape analysis on a minimal batch of
@@ -269,8 +183,14 @@ func (b *batch) initFunc(fn *ir.Func) {
// Allocate locations for local variables.
for _, n := range fn.Dcl {
- if n.Op() == ir.ONAME {
- e.newLoc(n, false)
+ e.newLoc(n, false)
+ }
+
+ // Also for hidden parameters (e.g., the ".this" parameter to a
+ // method value wrapper).
+ if fn.OClosure == nil {
+ for _, n := range fn.ClosureVars {
+ e.newLoc(n.Canonical(), false)
}
}
@@ -342,1316 +262,6 @@ func (b *batch) flowClosure(k hole, clo *ir.ClosureExpr) {
}
}
-// Below we implement the methods for walking the AST and recording
-// data flow edges. Note that because a sub-expression might have
-// side-effects, it's important to always visit the entire AST.
-//
-// For example, write either:
-//
-// if x {
-// e.discard(n.Left)
-// } else {
-// e.value(k, n.Left)
-// }
-//
-// or
-//
-// if x {
-// k = e.discardHole()
-// }
-// e.value(k, n.Left)
-//
-// Do NOT write:
-//
-// // BAD: possibly loses side-effects within n.Left
-// if !x {
-// e.value(k, n.Left)
-// }
-
-// stmt evaluates a single Go statement.
-func (e *escape) stmt(n ir.Node) {
- if n == nil {
- return
- }
-
- lno := ir.SetPos(n)
- defer func() {
- base.Pos = lno
- }()
-
- if base.Flag.LowerM > 2 {
- fmt.Printf("%v:[%d] %v stmt: %v\n", base.FmtPos(base.Pos), e.loopDepth, e.curfn, n)
- }
-
- e.stmts(n.Init())
-
- switch n.Op() {
- default:
- base.Fatalf("unexpected stmt: %v", n)
-
- case ir.ODCLCONST, ir.ODCLTYPE, ir.OFALL, ir.OINLMARK:
- // nop
-
- case ir.OBREAK, ir.OCONTINUE, ir.OGOTO:
- // TODO(mdempsky): Handle dead code?
-
- case ir.OBLOCK:
- n := n.(*ir.BlockStmt)
- e.stmts(n.List)
-
- case ir.ODCL:
- // Record loop depth at declaration.
- n := n.(*ir.Decl)
- if !ir.IsBlank(n.X) {
- e.dcl(n.X)
- }
-
- case ir.OLABEL:
- n := n.(*ir.LabelStmt)
- switch e.labels[n.Label] {
- case nonlooping:
- if base.Flag.LowerM > 2 {
- fmt.Printf("%v:%v non-looping label\n", base.FmtPos(base.Pos), n)
- }
- case looping:
- if base.Flag.LowerM > 2 {
- fmt.Printf("%v: %v looping label\n", base.FmtPos(base.Pos), n)
- }
- e.loopDepth++
- default:
- base.Fatalf("label missing tag")
- }
- delete(e.labels, n.Label)
-
- case ir.OIF:
- n := n.(*ir.IfStmt)
- e.discard(n.Cond)
- e.block(n.Body)
- e.block(n.Else)
-
- case ir.OFOR, ir.OFORUNTIL:
- n := n.(*ir.ForStmt)
- e.loopDepth++
- e.discard(n.Cond)
- e.stmt(n.Post)
- e.block(n.Body)
- e.loopDepth--
-
- case ir.ORANGE:
- // for Key, Value = range X { Body }
- n := n.(*ir.RangeStmt)
-
- // X is evaluated outside the loop.
- tmp := e.newLoc(nil, false)
- e.expr(tmp.asHole(), n.X)
-
- e.loopDepth++
- ks := e.addrs([]ir.Node{n.Key, n.Value})
- if n.X.Type().IsArray() {
- e.flow(ks[1].note(n, "range"), tmp)
- } else {
- e.flow(ks[1].deref(n, "range-deref"), tmp)
- }
- e.reassigned(ks, n)
-
- e.block(n.Body)
- e.loopDepth--
-
- case ir.OSWITCH:
- n := n.(*ir.SwitchStmt)
-
- if guard, ok := n.Tag.(*ir.TypeSwitchGuard); ok {
- var ks []hole
- if guard.Tag != nil {
- for _, cas := range n.Cases {
- cv := cas.Var
- k := e.dcl(cv) // type switch variables have no ODCL.
- if cv.Type().HasPointers() {
- ks = append(ks, k.dotType(cv.Type(), cas, "switch case"))
- }
- }
- }
- e.expr(e.teeHole(ks...), n.Tag.(*ir.TypeSwitchGuard).X)
- } else {
- e.discard(n.Tag)
- }
-
- for _, cas := range n.Cases {
- e.discards(cas.List)
- e.block(cas.Body)
- }
-
- case ir.OSELECT:
- n := n.(*ir.SelectStmt)
- for _, cas := range n.Cases {
- e.stmt(cas.Comm)
- e.block(cas.Body)
- }
- case ir.ORECV:
- // TODO(mdempsky): Consider e.discard(n.Left).
- n := n.(*ir.UnaryExpr)
- e.exprSkipInit(e.discardHole(), n) // already visited n.Ninit
- case ir.OSEND:
- n := n.(*ir.SendStmt)
- e.discard(n.Chan)
- e.assignHeap(n.Value, "send", n)
-
- case ir.OAS:
- n := n.(*ir.AssignStmt)
- e.assignList([]ir.Node{n.X}, []ir.Node{n.Y}, "assign", n)
- case ir.OASOP:
- n := n.(*ir.AssignOpStmt)
- // TODO(mdempsky): Worry about OLSH/ORSH?
- e.assignList([]ir.Node{n.X}, []ir.Node{n.Y}, "assign", n)
- case ir.OAS2:
- n := n.(*ir.AssignListStmt)
- e.assignList(n.Lhs, n.Rhs, "assign-pair", n)
-
- case ir.OAS2DOTTYPE: // v, ok = x.(type)
- n := n.(*ir.AssignListStmt)
- e.assignList(n.Lhs, n.Rhs, "assign-pair-dot-type", n)
- case ir.OAS2MAPR: // v, ok = m[k]
- n := n.(*ir.AssignListStmt)
- e.assignList(n.Lhs, n.Rhs, "assign-pair-mapr", n)
- case ir.OAS2RECV, ir.OSELRECV2: // v, ok = <-ch
- n := n.(*ir.AssignListStmt)
- e.assignList(n.Lhs, n.Rhs, "assign-pair-receive", n)
-
- case ir.OAS2FUNC:
- n := n.(*ir.AssignListStmt)
- e.stmts(n.Rhs[0].Init())
- ks := e.addrs(n.Lhs)
- e.call(ks, n.Rhs[0], nil)
- e.reassigned(ks, n)
- case ir.ORETURN:
- n := n.(*ir.ReturnStmt)
- results := e.curfn.Type().Results().FieldSlice()
- dsts := make([]ir.Node, len(results))
- for i, res := range results {
- dsts[i] = res.Nname.(*ir.Name)
- }
- e.assignList(dsts, n.Results, "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 ir.OGO, ir.ODEFER:
- n := n.(*ir.GoDeferStmt)
- e.stmts(n.Call.Init())
- e.call(nil, n.Call, n)
-
- case ir.OTAILCALL:
- // TODO(mdempsky): Treat like a normal call? esc.go used to just ignore it.
- }
-}
-
-func (e *escape) stmts(l ir.Nodes) {
- for _, n := range l {
- e.stmt(n)
- }
-}
-
-// block is like stmts, but preserves loopDepth.
-func (e *escape) block(l ir.Nodes) {
- old := e.loopDepth
- e.stmts(l)
- e.loopDepth = old
-}
-
-// expr models evaluating an expression n and flowing the result into
-// hole k.
-func (e *escape) expr(k hole, n ir.Node) {
- if n == nil {
- return
- }
- e.stmts(n.Init())
- e.exprSkipInit(k, n)
-}
-
-func (e *escape) exprSkipInit(k hole, n ir.Node) {
- if n == nil {
- return
- }
-
- lno := ir.SetPos(n)
- defer func() {
- base.Pos = lno
- }()
-
- uintptrEscapesHack := k.uintptrEscapesHack
- k.uintptrEscapesHack = false
-
- if uintptrEscapesHack && n.Op() == ir.OCONVNOP && n.(*ir.ConvExpr).X.Type().IsUnsafePtr() {
- // nop
- } else if k.derefs >= 0 && !n.Type().HasPointers() {
- k.dst = &e.blankLoc
- }
-
- switch n.Op() {
- default:
- base.Fatalf("unexpected expr: %s %v", n.Op().String(), n)
-
- case ir.OLITERAL, ir.ONIL, ir.OGETG, ir.OTYPE, ir.OMETHEXPR, ir.OLINKSYMOFFSET:
- // nop
-
- case ir.ONAME:
- n := n.(*ir.Name)
- if n.Class == ir.PFUNC || n.Class == ir.PEXTERN {
- return
- }
- if n.IsClosureVar() && n.Defn == nil {
- return // ".this" from method value wrapper
- }
- e.flow(k, e.oldLoc(n))
-
- case ir.OPLUS, ir.ONEG, ir.OBITNOT, ir.ONOT:
- n := n.(*ir.UnaryExpr)
- e.discard(n.X)
- 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:
- n := n.(*ir.BinaryExpr)
- e.discard(n.X)
- e.discard(n.Y)
- case ir.OANDAND, ir.OOROR:
- n := n.(*ir.LogicalExpr)
- e.discard(n.X)
- e.discard(n.Y)
- case ir.OADDR:
- n := n.(*ir.AddrExpr)
- e.expr(k.addr(n, "address-of"), n.X) // "address-of"
- case ir.ODEREF:
- n := n.(*ir.StarExpr)
- e.expr(k.deref(n, "indirection"), n.X) // "indirection"
- case ir.ODOT, ir.ODOTMETH, ir.ODOTINTER:
- n := n.(*ir.SelectorExpr)
- e.expr(k.note(n, "dot"), n.X)
- case ir.ODOTPTR:
- n := n.(*ir.SelectorExpr)
- e.expr(k.deref(n, "dot of pointer"), n.X) // "dot of pointer"
- case ir.ODOTTYPE, ir.ODOTTYPE2:
- n := n.(*ir.TypeAssertExpr)
- e.expr(k.dotType(n.Type(), n, "dot"), n.X)
- case ir.OINDEX:
- n := n.(*ir.IndexExpr)
- if n.X.Type().IsArray() {
- e.expr(k.note(n, "fixed-array-index-of"), n.X)
- } else {
- // TODO(mdempsky): Fix why reason text.
- e.expr(k.deref(n, "dot of pointer"), n.X)
- }
- e.discard(n.Index)
- case ir.OINDEXMAP:
- n := n.(*ir.IndexExpr)
- e.discard(n.X)
- e.discard(n.Index)
- case ir.OSLICE, ir.OSLICEARR, ir.OSLICE3, ir.OSLICE3ARR, ir.OSLICESTR:
- n := n.(*ir.SliceExpr)
- e.expr(k.note(n, "slice"), n.X)
- e.discard(n.Low)
- e.discard(n.High)
- e.discard(n.Max)
-
- case ir.OCONV, ir.OCONVNOP:
- n := n.(*ir.ConvExpr)
- if ir.ShouldCheckPtr(e.curfn, 2) && n.Type().IsUnsafePtr() && n.X.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.X, "conversion to unsafe.Pointer", n)
- } else if n.Type().IsUnsafePtr() && n.X.Type().IsUintptr() {
- e.unsafeValue(k, n.X)
- } else {
- e.expr(k, n.X)
- }
- case ir.OCONVIFACE:
- n := n.(*ir.ConvExpr)
- if !n.X.Type().IsInterface() && !types.IsDirectIface(n.X.Type()) {
- k = e.spill(k, n)
- }
- e.expr(k.note(n, "interface-converted"), n.X)
- case ir.OSLICE2ARRPTR:
- // the slice pointer flows directly to the result
- n := n.(*ir.ConvExpr)
- e.expr(k, n.X)
- case ir.ORECV:
- n := n.(*ir.UnaryExpr)
- e.discard(n.X)
-
- case ir.OCALLMETH, ir.OCALLFUNC, ir.OCALLINTER, ir.OLEN, ir.OCAP, ir.OCOMPLEX, ir.OREAL, ir.OIMAG, ir.OAPPEND, ir.OCOPY, ir.OUNSAFEADD, ir.OUNSAFESLICE:
- e.call([]hole{k}, n, nil)
-
- case ir.ONEW:
- n := n.(*ir.UnaryExpr)
- e.spill(k, n)
-
- case ir.OMAKESLICE:
- n := n.(*ir.MakeExpr)
- e.spill(k, n)
- e.discard(n.Len)
- e.discard(n.Cap)
- case ir.OMAKECHAN:
- n := n.(*ir.MakeExpr)
- e.discard(n.Len)
- case ir.OMAKEMAP:
- n := n.(*ir.MakeExpr)
- e.spill(k, n)
- e.discard(n.Len)
-
- case ir.ORECOVER:
- // nop
-
- case ir.OCALLPART:
- // Flow the receiver argument to both the closure and
- // to the receiver parameter.
-
- n := n.(*ir.SelectorExpr)
- closureK := e.spill(k, n)
-
- m := n.Selection
-
- // We don't know how the method value will be called
- // later, so conservatively assume the result
- // parameters all flow to the heap.
- //
- // TODO(mdempsky): Change ks into a callback, so that
- // we don't have to create this slice?
- var ks []hole
- for i := m.Type.NumResults(); i > 0; i-- {
- ks = append(ks, e.heapHole())
- }
- name, _ := m.Nname.(*ir.Name)
- paramK := e.tagHole(ks, name, m.Type.Recv())
-
- e.expr(e.teeHole(paramK, closureK), n.X)
-
- case ir.OPTRLIT:
- n := n.(*ir.AddrExpr)
- e.expr(e.spill(k, n), n.X)
-
- case ir.OARRAYLIT:
- n := n.(*ir.CompLitExpr)
- for _, elt := range n.List {
- if elt.Op() == ir.OKEY {
- elt = elt.(*ir.KeyExpr).Value
- }
- e.expr(k.note(n, "array literal element"), elt)
- }
-
- case ir.OSLICELIT:
- n := n.(*ir.CompLitExpr)
- k = e.spill(k, n)
- k.uintptrEscapesHack = uintptrEscapesHack // for ...uintptr parameters
-
- for _, elt := range n.List {
- if elt.Op() == ir.OKEY {
- elt = elt.(*ir.KeyExpr).Value
- }
- e.expr(k.note(n, "slice-literal-element"), elt)
- }
-
- case ir.OSTRUCTLIT:
- n := n.(*ir.CompLitExpr)
- for _, elt := range n.List {
- e.expr(k.note(n, "struct literal element"), elt.(*ir.StructKeyExpr).Value)
- }
-
- case ir.OMAPLIT:
- n := n.(*ir.CompLitExpr)
- e.spill(k, n)
-
- // Map keys and values are always stored in the heap.
- for _, elt := range n.List {
- elt := elt.(*ir.KeyExpr)
- e.assignHeap(elt.Key, "map literal key", n)
- e.assignHeap(elt.Value, "map literal value", n)
- }
-
- case ir.OCLOSURE:
- n := n.(*ir.ClosureExpr)
- k = e.spill(k, n)
- e.closures = append(e.closures, closure{k, n})
-
- if fn := n.Func; fn.IsHiddenClosure() {
- for _, cv := range fn.ClosureVars {
- if loc := e.oldLoc(cv); !loc.captured {
- loc.captured = true
-
- // Ignore reassignments to the variable in straightline code
- // preceding the first capture by a closure.
- if loc.loopDepth == e.loopDepth {
- loc.reassigned = false
- }
- }
- }
-
- for _, n := range fn.Dcl {
- // Add locations for local variables of the
- // closure, if needed, in case we're not including
- // the closure func in the batch for escape
- // analysis (happens for escape analysis called
- // from reflectdata.methodWrapper)
- if n.Op() == ir.ONAME && n.Opt == nil {
- e.with(fn).newLoc(n, false)
- }
- }
- e.walkFunc(fn)
- }
-
- case ir.ORUNES2STR, ir.OBYTES2STR, ir.OSTR2RUNES, ir.OSTR2BYTES, ir.ORUNESTR:
- n := n.(*ir.ConvExpr)
- e.spill(k, n)
- e.discard(n.X)
-
- case ir.OADDSTR:
- n := n.(*ir.AddStringExpr)
- e.spill(k, n)
-
- // Arguments of OADDSTR never escape;
- // runtime.concatstrings makes sure of that.
- e.discards(n.List)
- }
-}
-
-// unsafeValue evaluates a uintptr-typed arithmetic expression looking
-// for conversions from an unsafe.Pointer.
-func (e *escape) unsafeValue(k hole, n ir.Node) {
- if n.Type().Kind() != types.TUINTPTR {
- base.Fatalf("unexpected type %v for %v", n.Type(), n)
- }
- if k.addrtaken {
- base.Fatalf("unexpected addrtaken")
- }
-
- e.stmts(n.Init())
-
- switch n.Op() {
- case ir.OCONV, ir.OCONVNOP:
- n := n.(*ir.ConvExpr)
- if n.X.Type().IsUnsafePtr() {
- e.expr(k, n.X)
- } else {
- e.discard(n.X)
- }
- case ir.ODOTPTR:
- n := n.(*ir.SelectorExpr)
- if ir.IsReflectHeaderDataField(n) {
- e.expr(k.deref(n, "reflect.Header.Data"), n.X)
- } else {
- e.discard(n.X)
- }
- case ir.OPLUS, ir.ONEG, ir.OBITNOT:
- n := n.(*ir.UnaryExpr)
- e.unsafeValue(k, n.X)
- case ir.OADD, ir.OSUB, ir.OOR, ir.OXOR, ir.OMUL, ir.ODIV, ir.OMOD, ir.OAND, ir.OANDNOT:
- n := n.(*ir.BinaryExpr)
- e.unsafeValue(k, n.X)
- e.unsafeValue(k, n.Y)
- case ir.OLSH, ir.ORSH:
- n := n.(*ir.BinaryExpr)
- e.unsafeValue(k, n.X)
- // RHS need not be uintptr-typed (#32959) and can't meaningfully
- // flow pointers anyway.
- e.discard(n.Y)
- default:
- e.exprSkipInit(e.discardHole(), n)
- }
-}
-
-// discard evaluates an expression n for side-effects, but discards
-// its value.
-func (e *escape) discard(n ir.Node) {
- e.expr(e.discardHole(), n)
-}
-
-func (e *escape) discards(l ir.Nodes) {
- for _, n := range l {
- e.discard(n)
- }
-}
-
-// addr evaluates an addressable expression n and returns a hole
-// that represents storing into the represented location.
-func (e *escape) addr(n ir.Node) hole {
- if n == nil || ir.IsBlank(n) {
- // Can happen in select case, range, maybe others.
- return e.discardHole()
- }
-
- k := e.heapHole()
-
- switch n.Op() {
- default:
- base.Fatalf("unexpected addr: %v", n)
- case ir.ONAME:
- n := n.(*ir.Name)
- if n.Class == ir.PEXTERN {
- break
- }
- k = e.oldLoc(n).asHole()
- case ir.OLINKSYMOFFSET:
- break
- case ir.ODOT:
- n := n.(*ir.SelectorExpr)
- k = e.addr(n.X)
- case ir.OINDEX:
- n := n.(*ir.IndexExpr)
- e.discard(n.Index)
- if n.X.Type().IsArray() {
- k = e.addr(n.X)
- } else {
- e.discard(n.X)
- }
- case ir.ODEREF, ir.ODOTPTR:
- e.discard(n)
- case ir.OINDEXMAP:
- n := n.(*ir.IndexExpr)
- e.discard(n.X)
- e.assignHeap(n.Index, "key of map put", n)
- }
-
- return k
-}
-
-func (e *escape) addrs(l ir.Nodes) []hole {
- var ks []hole
- for _, n := range l {
- ks = append(ks, e.addr(n))
- }
- return ks
-}
-
-// reassigned marks the locations associated with the given holes as
-// reassigned, unless the location represents a variable declared and
-// assigned exactly once by where.
-func (e *escape) reassigned(ks []hole, where ir.Node) {
- if as, ok := where.(*ir.AssignStmt); ok && as.Op() == ir.OAS && as.Y == nil {
- if dst, ok := as.X.(*ir.Name); ok && dst.Op() == ir.ONAME && dst.Defn == nil {
- // Zero-value assignment for variable declared without an
- // explicit initial value. Assume this is its initialization
- // statement.
- return
- }
- }
-
- for _, k := range ks {
- loc := k.dst
- // Variables declared by range statements are assigned on every iteration.
- if n, ok := loc.n.(*ir.Name); ok && n.Defn == where && where.Op() != ir.ORANGE {
- continue
- }
- loc.reassigned = true
- }
-}
-
-// assignList evaluates the assignment dsts... = srcs....
-func (e *escape) assignList(dsts, srcs []ir.Node, why string, where ir.Node) {
- ks := e.addrs(dsts)
- for i, k := range ks {
- var src ir.Node
- if i < len(srcs) {
- src = srcs[i]
- }
-
- if dst := dsts[i]; dst != nil {
- // Detect implicit conversion of uintptr to unsafe.Pointer when
- // storing into reflect.{Slice,String}Header.
- if dst.Op() == ir.ODOTPTR && ir.IsReflectHeaderDataField(dst) {
- e.unsafeValue(e.heapHole().note(where, why), src)
- continue
- }
-
- // Filter out some no-op assignments for escape analysis.
- if src != nil && isSelfAssign(dst, src) {
- if base.Flag.LowerM != 0 {
- base.WarnfAt(where.Pos(), "%v ignoring self-assignment in %v", e.curfn, where)
- }
- k = e.discardHole()
- }
- }
-
- e.expr(k.note(where, why), src)
- }
-
- e.reassigned(ks, where)
-}
-
-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 []hole, 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.SetEsc(ir.EscNever)
- }
-
- argument := func(k hole, arg ir.Node) {
- if topLevelDefer {
- // Top level defers arguments don't escape to
- // heap, but they do need to last until end of
- // function.
- k = e.later(k)
- } else if where != nil {
- k = e.heapHole()
- }
-
- e.expr(k.note(call, "call parameter"), arg)
- }
-
- switch call.Op() {
- default:
- ir.Dump("esc", call)
- base.Fatalf("unexpected call op: %v", call.Op())
-
- case ir.OCALLFUNC, ir.OCALLMETH, ir.OCALLINTER:
- call := call.(*ir.CallExpr)
- typecheck.FixVariadicCall(call)
-
- // Pick out the function callee, if statically known.
- var fn *ir.Name
- switch call.Op() {
- case ir.OCALLFUNC:
- switch v := ir.StaticValue(call.X); {
- case v.Op() == ir.ONAME && v.(*ir.Name).Class == ir.PFUNC:
- fn = v.(*ir.Name)
- case v.Op() == ir.OCLOSURE:
- fn = v.(*ir.ClosureExpr).Func.Nname
- }
- case ir.OCALLMETH:
- fn = ir.MethodExprName(call.X)
- }
-
- fntype := call.X.Type()
- if fn != nil {
- fntype = fn.Type()
- }
-
- if ks != nil && fn != nil && e.inMutualBatch(fn) {
- 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.X.(*ir.SelectorExpr).X)
- } else {
- // Evaluate callee function expression.
- argument(e.discardHole(), call.X)
- }
-
- args := call.Args
- for i, param := range fntype.Params().FieldSlice() {
- argument(e.tagHole(ks, fn, param), args[i])
- }
-
- case ir.OAPPEND:
- call := call.(*ir.CallExpr)
- args := call.Args
-
- // 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() {
- 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() {
- appendedK = e.heapHole().deref(call, "appended slice...")
- }
- argument(appendedK, args[1])
- } else {
- for _, arg := range args[1:] {
- argument(e.heapHole(), arg)
- }
- }
-
- case ir.OCOPY:
- call := call.(*ir.BinaryExpr)
- argument(e.discardHole(), call.X)
-
- copiedK := e.discardHole()
- if call.Y.Type().IsSlice() && call.Y.Type().Elem().HasPointers() {
- copiedK = e.heapHole().deref(call, "copied slice")
- }
- argument(copiedK, call.Y)
-
- case ir.OPANIC:
- call := call.(*ir.UnaryExpr)
- argument(e.heapHole(), call.X)
-
- case ir.OCOMPLEX:
- call := call.(*ir.BinaryExpr)
- argument(e.discardHole(), call.X)
- argument(e.discardHole(), call.Y)
- case ir.ODELETE, ir.OPRINT, ir.OPRINTN, ir.ORECOVER:
- call := call.(*ir.CallExpr)
- for _, arg := range call.Args {
- argument(e.discardHole(), arg)
- }
- case ir.OLEN, ir.OCAP, ir.OREAL, ir.OIMAG, ir.OCLOSE:
- call := call.(*ir.UnaryExpr)
- argument(e.discardHole(), call.X)
-
- case ir.OUNSAFEADD, ir.OUNSAFESLICE:
- call := call.(*ir.BinaryExpr)
- argument(ks[0], call.X)
- argument(e.discardHole(), call.Y)
- }
-}
-
-// tagHole returns a hole for evaluating an argument passed to param.
-// 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 []hole, fn *ir.Name, param *types.Field) hole {
- // 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(ir.AsNode(param.Nname))
- }
-
- // Call to previously tagged function.
-
- if param.Note == UintptrEscapesNote {
- k := e.heapHole()
- k.uintptrEscapesHack = true
- return k
- }
-
- var tagKs []hole
-
- esc := parseLeaks(param.Note)
- if x := esc.Heap(); x >= 0 {
- tagKs = append(tagKs, e.heapHole().shift(x))
- }
-
- if ks != nil {
- for i := 0; i < numEscResults; i++ {
- if x := esc.Result(i); x >= 0 {
- tagKs = append(tagKs, ks[i].shift(x))
- }
- }
- }
-
- return e.teeHole(tagKs...)
-}
-
-// inMutualBatch reports whether function fn is in the batch of
-// mutually recursive functions being analyzed. When this is true,
-// 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 *ir.Name) bool {
- if fn.Defn != nil && fn.Defn.Esc() < escFuncTagged {
- if fn.Defn.Esc() == escFuncUnknown {
- base.Fatalf("graph inconsistency: %v", fn)
- }
- return true
- }
- return false
-}
-
-// An hole represents a context for evaluation a Go
-// expression. E.g., when evaluating p in "x = **p", we'd have a hole
-// with dst==x and derefs==2.
-type hole struct {
- dst *location
- derefs int // >= -1
- notes *note
-
- // addrtaken indicates whether this context is taking the address of
- // the expression, independent of whether the address will actually
- // be stored into a variable.
- addrtaken bool
-
- // uintptrEscapesHack indicates this context is evaluating an
- // argument for a //go:uintptrescapes function.
- uintptrEscapesHack bool
-}
-
-type note struct {
- next *note
- where ir.Node
- why string
-}
-
-func (k hole) note(where ir.Node, why string) hole {
- if where == nil || why == "" {
- base.Fatalf("note: missing where/why")
- }
- if base.Flag.LowerM >= 2 || logopt.Enabled() {
- k.notes = &note{
- next: k.notes,
- where: where,
- why: why,
- }
- }
- return k
-}
-
-func (k hole) shift(delta int) hole {
- k.derefs += delta
- if k.derefs < -1 {
- base.Fatalf("derefs underflow: %v", k.derefs)
- }
- k.addrtaken = delta < 0
- return k
-}
-
-func (k hole) deref(where ir.Node, why string) hole { return k.shift(1).note(where, why) }
-func (k hole) addr(where ir.Node, why string) hole { return k.shift(-1).note(where, why) }
-
-func (k hole) dotType(t *types.Type, where ir.Node, why string) hole {
- if !t.IsInterface() && !types.IsDirectIface(t) {
- k = k.shift(1)
- }
- return k.note(where, why)
-}
-
-// teeHole returns a new hole that flows into each hole of ks,
-// similar to the Unix tee(1) command.
-func (e *escape) teeHole(ks ...hole) hole {
- if len(ks) == 0 {
- return e.discardHole()
- }
- if len(ks) == 1 {
- return ks[0]
- }
- // TODO(mdempsky): Optimize if there's only one non-discard hole?
-
- // Given holes "l1 = _", "l2 = **_", "l3 = *_", ..., create a
- // new temporary location ltmp, wire it into place, and return
- // a hole for "ltmp = _".
- loc := e.newLoc(nil, true)
- for _, k := range ks {
- // N.B., "p = &q" and "p = &tmp; tmp = q" are not
- // semantically equivalent. To combine holes like "l1
- // = _" and "l2 = &_", we'd need to wire them as "l1 =
- // *ltmp" and "l2 = ltmp" and return "ltmp = &_"
- // instead.
- if k.derefs < 0 {
- base.Fatalf("teeHole: negative derefs")
- }
-
- e.flow(k, loc)
- }
- return loc.asHole()
-}
-
-func (e *escape) dcl(n *ir.Name) hole {
- if n.Curfn != e.curfn || n.IsClosureVar() {
- base.Fatalf("bad declaration of %v", n)
- }
- loc := e.oldLoc(n)
- loc.loopDepth = e.loopDepth
- return loc.asHole()
-}
-
-// 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 hole, n ir.Node) hole {
- loc := e.newLoc(n, true)
- e.flow(k.addr(n, "spill"), loc)
- return loc.asHole()
-}
-
-// later returns a new hole that flows into k, but some time later.
-// Its main effect is to prevent immediate reuse of temporary
-// variables introduced during Order.
-func (e *escape) later(k hole) hole {
- loc := e.newLoc(nil, false)
- e.flow(k, loc)
- return loc.asHole()
-}
-
-func (e *escape) newLoc(n ir.Node, transient bool) *location {
- if e.curfn == nil {
- base.Fatalf("e.curfn isn't set")
- }
- if n != nil && n.Type() != nil && n.Type().NotInHeap() {
- base.ErrorfAt(n.Pos(), "%v is incomplete (or unallocatable); stack allocation disallowed", n.Type())
- }
-
- if n != nil && n.Op() == ir.ONAME {
- n = n.(*ir.Name).Canonical()
- }
- loc := &location{
- n: n,
- curfn: e.curfn,
- loopDepth: e.loopDepth,
- transient: transient,
- }
- e.allLocs = append(e.allLocs, loc)
- if n != nil {
- if n.Op() == ir.ONAME {
- n := n.(*ir.Name)
- if n.Curfn != e.curfn {
- base.Fatalf("curfn mismatch: %v != %v for %v", n.Curfn, e.curfn, n)
- }
-
- if n.Opt != nil {
- base.Fatalf("%v already has a location", n)
- }
- n.Opt = loc
- }
- }
- return loc
-}
-
-func (b *batch) oldLoc(n *ir.Name) *location {
- if n.Canonical().Opt == nil {
- base.Fatalf("%v has no location", n)
- }
- return n.Canonical().Opt.(*location)
-}
-
-func (l *location) asHole() hole {
- return hole{dst: l}
-}
-
-func (b *batch) flow(k hole, src *location) {
- if k.addrtaken {
- src.addrtaken = true
- }
-
- dst := k.dst
- if dst == &b.blankLoc {
- return
- }
- if dst == src && k.derefs >= 0 { // dst = dst, dst = *dst, ...
- return
- }
- if dst.escapes && k.derefs < 0 { // dst = &src
- 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 := b.explainFlow(pos, dst, src, k.derefs, k.notes, []*logopt.LoggedOpt{})
- if logopt.Enabled() {
- var e_curfn *ir.Func // TODO(mdempsky): Fix.
- logopt.LogOpt(src.n.Pos(), "escapes", "escape", ir.FuncName(e_curfn), fmt.Sprintf("%v escapes to heap", src.n), explanation)
- }
-
- }
- src.escapes = true
- return
- }
-
- // TODO(mdempsky): Deduplicate edges?
- dst.edges = append(dst.edges, edge{src: src, derefs: k.derefs, notes: k.notes})
-}
-
-func (b *batch) heapHole() hole { return b.heapLoc.asHole() }
-func (b *batch) discardHole() hole { return b.blankLoc.asHole() }
-
-// walkAll computes the minimal dereferences between all pairs of
-// locations.
-func (b *batch) walkAll() {
- // We use a work queue to keep track of locations that we need
- // to visit, and repeatedly walk until we reach a fixed point.
- //
- // We walk once from each location (including the heap), and
- // then re-enqueue each location on its transition from
- // transient->!transient and !escapes->escapes, which can each
- // happen at most once. So we take Θ(len(e.allLocs)) walks.
-
- // LIFO queue, has enough room for e.allLocs and e.heapLoc.
- todo := make([]*location, 0, len(b.allLocs)+1)
- enqueue := func(loc *location) {
- if !loc.queued {
- todo = append(todo, loc)
- loc.queued = true
- }
- }
-
- for _, loc := range b.allLocs {
- enqueue(loc)
- }
- enqueue(&b.heapLoc)
-
- var walkgen uint32
- for len(todo) > 0 {
- root := todo[len(todo)-1]
- todo = todo[:len(todo)-1]
- root.queued = false
-
- walkgen++
- b.walkOne(root, walkgen, enqueue)
- }
-}
-
-// walkOne computes the minimal number of dereferences from root to
-// all other locations.
-func (b *batch) walkOne(root *location, walkgen uint32, enqueue func(*location)) {
- // The data flow graph has negative edges (from addressing
- // operations), so we use the Bellman-Ford algorithm. However,
- // we don't have to worry about infinite negative cycles since
- // we bound intermediate dereference counts to 0.
-
- root.walkgen = walkgen
- root.derefs = 0
- root.dst = nil
-
- todo := []*location{root} // LIFO queue
- for len(todo) > 0 {
- l := todo[len(todo)-1]
- todo = todo[:len(todo)-1]
-
- derefs := l.derefs
-
- // If l.derefs < 0, then l's address flows to root.
- 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
- // derefs at 0.
- derefs = 0
-
- // If l's address flows to a non-transient
- // location, then l can't be transiently
- // allocated.
- if !root.transient && l.transient {
- l.transient = false
- enqueue(l)
- }
- }
-
- if b.outlives(root, l) {
- // l's value flows to root. If l is a function
- // parameter and root is the heap or a
- // corresponding result parameter, then record
- // that value flow for tagging the function
- // later.
- 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, b.explainLoc(root), derefs)
- }
- explanation := b.explainPath(root, l)
- if logopt.Enabled() {
- var e_curfn *ir.Func // TODO(mdempsky): Fix.
- logopt.LogOpt(l.n.Pos(), "leak", "escape", ir.FuncName(e_curfn),
- fmt.Sprintf("parameter %v leaks to %s with derefs=%d", l.n, b.explainLoc(root), derefs), explanation)
- }
- }
- 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() || 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 := b.explainPath(root, l)
- if logopt.Enabled() {
- var e_curfn *ir.Func // TODO(mdempsky): Fix.
- logopt.LogOpt(l.n.Pos(), "escape", "escape", ir.FuncName(e_curfn), fmt.Sprintf("%v escapes to heap", l.n), explanation)
- }
- }
- l.escapes = true
- enqueue(l)
- continue
- }
- }
-
- for i, edge := range l.edges {
- if edge.src.escapes {
- continue
- }
- d := derefs + edge.derefs
- if edge.src.walkgen != walkgen || edge.src.derefs > d {
- edge.src.walkgen = walkgen
- edge.src.derefs = d
- edge.src.dst = l
- edge.src.dstEdgeIdx = i
- todo = append(todo, edge.src)
- }
- }
- }
-}
-
-// explainPath prints an explanation of how src flows to the walk root.
-func (b *batch) explainPath(root, src *location) []*logopt.LoggedOpt {
- visited := make(map[*location]bool)
- pos := base.FmtPos(src.n.Pos())
- var explanation []*logopt.LoggedOpt
- for {
- // Prevent infinite loop.
- if visited[src] {
- if base.Flag.LowerM >= 2 {
- fmt.Printf("%s: warning: truncated explanation due to assignment cycle; see golang.org/issue/35518\n", pos)
- }
- break
- }
- visited[src] = true
- dst := src.dst
- edge := &dst.edges[src.dstEdgeIdx]
- if edge.src != src {
- base.Fatalf("path inconsistency: %v != %v", edge.src, src)
- }
-
- explanation = b.explainFlow(pos, dst, src, edge.derefs, edge.notes, explanation)
-
- if dst == root {
- break
- }
- src = dst
- }
-
- return explanation
-}
-
-func (b *batch) explainFlow(pos string, dst, srcloc *location, derefs int, notes *note, explanation []*logopt.LoggedOpt) []*logopt.LoggedOpt {
- ops := "&"
- if derefs >= 0 {
- ops = strings.Repeat("*", derefs)
- }
- print := base.Flag.LowerM >= 2
-
- flow := fmt.Sprintf(" flow: %s = %s%v:", b.explainLoc(dst), ops, b.explainLoc(srcloc))
- if print {
- fmt.Printf("%s:%s\n", pos, flow)
- }
- if logopt.Enabled() {
- var epos src.XPos
- if notes != nil {
- epos = notes.where.Pos()
- } else if srcloc != nil && srcloc.n != nil {
- epos = srcloc.n.Pos()
- }
- var e_curfn *ir.Func // TODO(mdempsky): Fix.
- 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, base.FmtPos(note.where.Pos()))
- }
- if logopt.Enabled() {
- var e_curfn *ir.Func // TODO(mdempsky): Fix.
- explanation = append(explanation, logopt.NewLoggedOpt(note.where.Pos(), "escflow", "escape", ir.FuncName(e_curfn),
- fmt.Sprintf(" from %v (%v)", note.where, note.why)))
- }
- }
- return explanation
-}
-
-func (b *batch) explainLoc(l *location) string {
- if l == &b.heapLoc {
- return "{heap}"
- }
- if l.n == nil {
- // TODO(mdempsky): Omit entirely.
- return "{temp}"
- }
- if l.n.Op() == ir.ONAME {
- return fmt.Sprintf("%v", l.n)
- }
- return fmt.Sprintf("{storage for %v}", l.n)
-}
-
-// outlives reports whether values stored in l may survive beyond
-// other's lifetime if stack allocated.
-func (b *batch) outlives(l, other *location) bool {
- // The heap outlives everything.
- if l.escapes {
- return true
- }
-
- // 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(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.ClosureCalled() {
- return false
- }
-
- return true
- }
-
- // If l and other are within the same function, then l
- // outlives other if it was declared outside other's loop
- // scope. For example:
- //
- // var l *int
- // for {
- // l = new(int)
- // }
- if l.curfn == other.curfn && l.loopDepth < other.loopDepth {
- return true
- }
-
- // If other is declared within a child closure of where l is
- // declared, then l outlives it. For example:
- //
- // var l *int
- // func() {
- // l = new(int)
- // }
- if containsClosure(l.curfn, other.curfn) {
- return true
- }
-
- return false
-}
-
-// containsClosure reports whether c is a closure contained within f.
-func containsClosure(f, c *ir.Func) bool {
- // Common case.
- if f == c {
- return false
- }
-
- // Closures within function Foo are named like "Foo.funcN..."
- // TODO(mdempsky): Better way to recognize this.
- fn := f.Sym().Name
- cn := c.Sym().Name
- return len(cn) > len(fn) && cn[:len(fn)] == fn && cn[len(fn)] == '.'
-}
-
-// leak records that parameter l leaks to sink.
-func (l *location) leakTo(sink *location, derefs int) {
- // If sink is a result parameter that doesn't escape (#44614)
- // and we can fit return bits into the escape analysis tag,
- // then record as a result leak.
- if !sink.escapes && sink.isName(ir.PPARAMOUT) && sink.curfn == l.curfn {
- ri := sink.resultIndex - 1
- if ri < numEscResults {
- // Leak to result parameter.
- l.paramEsc.AddResult(ri, derefs)
- return
- }
- }
-
- // Otherwise, record as heap leak.
- l.paramEsc.AddHeap(derefs)
-}
-
func (b *batch) finish(fns []*ir.Func) {
// Record parameter tags for package export data.
for _, fn := range fns {
@@ -1678,6 +288,11 @@ func (b *batch) finish(fns []*ir.Func) {
// Update n.Esc based on escape analysis results.
+ // Omit escape diagnostics for go/defer wrappers, at least for now.
+ // Historically, we haven't printed them, and test cases don't expect them.
+ // TODO(mdempsky): Update tests to expect this.
+ goDeferWrapper := n.Op() == ir.OCLOSURE && n.(*ir.ClosureExpr).Func.Wrapper()
+
if loc.escapes {
if n.Op() == ir.ONAME {
if base.Flag.CompilingRuntime {
@@ -1687,7 +302,7 @@ func (b *batch) finish(fns []*ir.Func) {
base.WarnfAt(n.Pos(), "moved to heap: %v", n)
}
} else {
- if base.Flag.LowerM != 0 {
+ if base.Flag.LowerM != 0 && !goDeferWrapper {
base.WarnfAt(n.Pos(), "%v escapes to heap", n)
}
if logopt.Enabled() {
@@ -1697,7 +312,7 @@ func (b *batch) finish(fns []*ir.Func) {
}
n.SetEsc(ir.EscHeap)
} else {
- if base.Flag.LowerM != 0 && n.Op() != ir.ONAME {
+ if base.Flag.LowerM != 0 && n.Op() != ir.ONAME && !goDeferWrapper {
base.WarnfAt(n.Pos(), "%v does not escape", n)
}
n.SetEsc(ir.EscNone)
@@ -1706,7 +321,7 @@ func (b *batch) finish(fns []*ir.Func) {
case ir.OCLOSURE:
n := n.(*ir.ClosureExpr)
n.SetTransient(true)
- case ir.OCALLPART:
+ case ir.OMETHVALUE:
n := n.(*ir.SelectorExpr)
n.SetTransient(true)
case ir.OSLICELIT:
@@ -1718,107 +333,19 @@ func (b *batch) finish(fns []*ir.Func) {
}
}
-func (l *location) isName(c ir.Class) bool {
- return l.n != nil && l.n.Op() == ir.ONAME && l.n.(*ir.Name).Class == c
-}
-
-const numEscResults = 7
-
-// An leaks represents a set of assignment flows from a parameter
-// to the heap or to any of its function's (first numEscResults)
-// result parameters.
-type leaks [1 + numEscResults]uint8
-
-// Empty reports whether l is an empty set (i.e., no assignment flows).
-func (l leaks) Empty() bool { return l == leaks{} }
-
-// Heap returns the minimum deref count of any assignment flow from l
-// to the heap. If no such flows exist, Heap returns -1.
-func (l leaks) Heap() int { return l.get(0) }
-
-// Result returns the minimum deref count of any assignment flow from
-// l to its function's i'th result parameter. If no such flows exist,
-// Result returns -1.
-func (l leaks) Result(i int) int { return l.get(1 + i) }
-
-// AddHeap adds an assignment flow from l to the heap.
-func (l *leaks) AddHeap(derefs int) { l.add(0, derefs) }
-
-// AddResult adds an assignment flow from l to its function's i'th
-// result parameter.
-func (l *leaks) AddResult(i, derefs int) { l.add(1+i, derefs) }
-
-func (l *leaks) setResult(i, derefs int) { l.set(1+i, derefs) }
-
-func (l leaks) get(i int) int { return int(l[i]) - 1 }
-
-func (l *leaks) add(i, derefs int) {
- if old := l.get(i); old < 0 || derefs < old {
- l.set(i, derefs)
- }
-}
-
-func (l *leaks) set(i, derefs int) {
- v := derefs + 1
- if v < 0 {
- base.Fatalf("invalid derefs count: %v", derefs)
- }
- if v > math.MaxUint8 {
- v = math.MaxUint8
- }
-
- l[i] = uint8(v)
-}
-
-// Optimize removes result flow paths that are equal in length or
-// longer than the shortest heap flow path.
-func (l *leaks) Optimize() {
- // If we have a path to the heap, then there's no use in
- // keeping equal or longer paths elsewhere.
- if x := l.Heap(); x >= 0 {
- for i := 0; i < numEscResults; i++ {
- if l.Result(i) >= x {
- l.setResult(i, -1)
- }
+// inMutualBatch reports whether function fn is in the batch of
+// mutually recursive functions being analyzed. When this is true,
+// 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 *ir.Name) bool {
+ if fn.Defn != nil && fn.Defn.Esc() < escFuncTagged {
+ if fn.Defn.Esc() == escFuncUnknown {
+ base.Fatalf("graph inconsistency: %v", fn)
}
+ return true
}
-}
-
-var leakTagCache = map[leaks]string{}
-
-// Encode converts l into a binary string for export data.
-func (l leaks) Encode() string {
- if l.Heap() == 0 {
- // Space optimization: empty string encodes more
- // efficiently in export data.
- return ""
- }
- if s, ok := leakTagCache[l]; ok {
- return s
- }
-
- n := len(l)
- for n > 0 && l[n-1] == 0 {
- n--
- }
- s := "esc:" + string(l[:n])
- leakTagCache[l] = s
- return s
-}
-
-// parseLeaks parses a binary string representing a leaks
-func parseLeaks(s string) leaks {
- var l leaks
- if !strings.HasPrefix(s, "esc:") {
- l.AddHeap(0)
- return l
- }
- copy(l[:], s[4:])
- return l
-}
-
-func Funcs(all []ir.Node) {
- ir.VisitFuncsBottomUp(all, Batch)
+ return false
}
const (
@@ -1836,220 +363,6 @@ const (
nonlooping
)
-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.
- var dstX ir.Node
- switch dst.Op() {
- default:
- return false
- case ir.ODEREF:
- dst := dst.(*ir.StarExpr)
- dstX = dst.X
- case ir.ODOTPTR:
- dst := dst.(*ir.SelectorExpr)
- dstX = dst.X
- }
- if dstX.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.
- src := src.(*ir.SliceExpr)
- if src.X.Op() == ir.OADDR {
- return false
- }
- default:
- return false
- }
- // slice is applied to ONAME dereference.
- var baseX ir.Node
- switch base := src.(*ir.SliceExpr).X; base.Op() {
- default:
- return false
- case ir.ODEREF:
- base := base.(*ir.StarExpr)
- baseX = base.X
- case ir.ODOTPTR:
- base := base.(*ir.SelectorExpr)
- baseX = base.X
- }
- if baseX.Op() != ir.ONAME {
- return false
- }
- // dst and src reference the same base ONAME.
- return dstX.(*ir.Name) == baseX.(*ir.Name)
-}
-
-// 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
- }
-
- // The expression prefix must be both "safe" and identical.
- switch dst.Op() {
- case ir.ODOT, ir.ODOTPTR:
- // Safe trailing accessors that are permitted to differ.
- dst := dst.(*ir.SelectorExpr)
- src := src.(*ir.SelectorExpr)
- return ir.SameSafeExpr(dst.X, src.X)
- case ir.OINDEX:
- dst := dst.(*ir.IndexExpr)
- src := src.(*ir.IndexExpr)
- if mayAffectMemory(dst.Index) || mayAffectMemory(src.Index) {
- return false
- }
- return ir.SameSafeExpr(dst.X, src.X)
- default:
- return false
- }
-}
-
-// 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.
-
- // TODO(rsc): It seems like it should be possible to replace this with
- // an ir.Any looking for any op that's not the ones in the case statement.
- // But that produces changes in the compiled output detected by buildall.
- switch n.Op() {
- case ir.ONAME, ir.OLITERAL, ir.ONIL:
- return false
-
- case ir.OADD, ir.OSUB, ir.OOR, ir.OXOR, ir.OMUL, ir.OLSH, ir.ORSH, ir.OAND, ir.OANDNOT, ir.ODIV, ir.OMOD:
- n := n.(*ir.BinaryExpr)
- return mayAffectMemory(n.X) || mayAffectMemory(n.Y)
-
- case ir.OINDEX:
- n := n.(*ir.IndexExpr)
- return mayAffectMemory(n.X) || mayAffectMemory(n.Index)
-
- case ir.OCONVNOP, ir.OCONV:
- n := n.(*ir.ConvExpr)
- return mayAffectMemory(n.X)
-
- case ir.OLEN, ir.OCAP, ir.ONOT, ir.OBITNOT, ir.OPLUS, ir.ONEG, ir.OALIGNOF, ir.OOFFSETOF, ir.OSIZEOF:
- n := n.(*ir.UnaryExpr)
- return mayAffectMemory(n.X)
-
- case ir.ODOT, ir.ODOTPTR:
- n := n.(*ir.SelectorExpr)
- return mayAffectMemory(n.X)
-
- case ir.ODEREF:
- n := n.(*ir.StarExpr)
- return mayAffectMemory(n.X)
-
- 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 == nil || n.Type() == nil {
- return ""
- }
-
- // Parameters are always passed via the stack.
- if n.Op() == ir.ONAME {
- n := n.(*ir.Name)
- if n.Class == ir.PPARAM || n.Class == ir.PPARAMOUT {
- return ""
- }
- }
-
- if n.Type().Width > ir.MaxStackVarSize {
- return "too large for stack"
- }
-
- if (n.Op() == ir.ONEW || n.Op() == ir.OPTRLIT) && n.Type().Elem().Width > ir.MaxImplicitStackVarSize {
- return "too large for stack"
- }
-
- if n.Op() == ir.OCLOSURE && typecheck.ClosureType(n.(*ir.ClosureExpr)).Size() > ir.MaxImplicitStackVarSize {
- return "too large for stack"
- }
- if n.Op() == ir.OCALLPART && typecheck.PartialCallType(n.(*ir.SelectorExpr)).Size() > ir.MaxImplicitStackVarSize {
- return "too large for stack"
- }
-
- if n.Op() == ir.OMAKESLICE {
- n := n.(*ir.MakeExpr)
- r := n.Cap
- if r == nil {
- r = n.Len
- }
- if !ir.IsSmallIntConst(r) {
- return "non-constant size"
- }
- if t := n.Type(); t.Elem().Width != 0 && ir.Int64Val(r) > ir.MaxImplicitStackVarSize/t.Elem().Width {
- return "too large for stack"
- }
- }
-
- return ""
-}
-
-// This special tag is applied to uintptr variables
-// that we believe may hold unsafe.Pointers for
-// calls into assembly functions.
-const UnsafeUintptrNote = "unsafe-uintptr"
-
-// This special tag is applied to uintptr parameters of functions
-// marked go:uintptrescapes.
-const UintptrEscapesNote = "uintptr-escapes"
-
func (b *batch) paramTag(fn *ir.Func, narg int, f *types.Field) string {
name := func() string {
if f.Sym != nil {
@@ -2058,6 +371,11 @@ func (b *batch) paramTag(fn *ir.Func, narg int, f *types.Field) string {
return fmt.Sprintf("arg#%d", narg)
}
+ // Only report diagnostics for user code;
+ // not for wrappers generated around them.
+ // TODO(mdempsky): Generalize this.
+ diagnose := base.Flag.LowerM != 0 && !(fn.Wrapper() || fn.Dupok())
+
if len(fn.Body) == 0 {
// Assume that uintptr arguments must be held live across the call.
// This is most important for syscall.Syscall.
@@ -2065,11 +383,13 @@ func (b *batch) paramTag(fn *ir.Func, narg int, f *types.Field) string {
// 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.
+ fn.Pragma |= ir.UintptrKeepAlive
+
if f.Type.IsUintptr() {
- if base.Flag.LowerM != 0 {
+ if diagnose {
base.WarnfAt(f.Pos, "assuming %v is unsafe uintptr", name())
}
- return UnsafeUintptrNote
+ return ""
}
if !f.Type.HasPointers() { // don't bother tagging for scalars
@@ -2081,11 +401,11 @@ func (b *batch) paramTag(fn *ir.Func, narg int, f *types.Field) string {
// External functions are assumed unsafe, unless
// //go:noescape is given before the declaration.
if fn.Pragma&ir.Noescape != 0 {
- if base.Flag.LowerM != 0 && f.Sym != nil {
+ if diagnose && f.Sym != nil {
base.WarnfAt(f.Pos, "%v does not escape", name())
}
} else {
- if base.Flag.LowerM != 0 && f.Sym != nil {
+ if diagnose && f.Sym != nil {
base.WarnfAt(f.Pos, "leaking param: %v", name())
}
esc.AddHeap(0)
@@ -2095,18 +415,20 @@ func (b *batch) paramTag(fn *ir.Func, narg int, f *types.Field) string {
}
if fn.Pragma&ir.UintptrEscapes != 0 {
+ fn.Pragma |= ir.UintptrKeepAlive
+
if f.Type.IsUintptr() {
- if base.Flag.LowerM != 0 {
+ if diagnose {
base.WarnfAt(f.Pos, "marking %v as escaping uintptr", name())
}
- return UintptrEscapesNote
+ return ""
}
if f.IsDDD() && f.Type.Elem().IsUintptr() {
// final argument is ...uintptr.
- if base.Flag.LowerM != 0 {
+ if diagnose {
base.WarnfAt(f.Pos, "marking %v as escaping ...uintptr", name())
}
- return UintptrEscapesNote
+ return ""
}
}
@@ -2125,7 +447,7 @@ func (b *batch) paramTag(fn *ir.Func, narg int, f *types.Field) string {
esc := loc.paramEsc
esc.Optimize()
- if base.Flag.LowerM != 0 && !loc.escapes {
+ if diagnose && !loc.escapes {
if esc.Empty() {
base.WarnfAt(f.Pos, "%v does not escape", name())
}