// Copyright 2011 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package gc import ( "cmd/compile/internal/types" "fmt" ) func escapes(all []*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 *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 Node nonlooping Node ) func isSliceSelfAssign(dst, src *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 != ODEREF && dst.Op != ODOTPTR || dst.Left.Op != ONAME { return false } // src is a slice operation. switch src.Op { case OSLICE, OSLICE3, OSLICESTR: // OK. case OSLICEARR, 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 == OADDR { return false } default: return false } // slice is applied to ONAME dereference. if src.Left.Op != ODEREF && src.Left.Op != ODOTPTR || src.Left.Left.Op != 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 *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 ODOT, ODOTPTR: // Safe trailing accessors that are permitted to differ. case 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 *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 ONAME, OCLOSUREVAR, OLITERAL: return false // Left+Right group. case OINDEX, OADD, OSUB, OOR, OXOR, OMUL, OLSH, ORSH, OAND, OANDNOT, ODIV, OMOD: return mayAffectMemory(n.Left) || mayAffectMemory(n.Right) // Left group. case ODOT, ODOTPTR, ODEREF, OCONVNOP, OCONV, OLEN, OCAP, ONOT, OBITNOT, OPLUS, ONEG, OALIGNOF, OOFFSETOF, 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 *Node) string { if n.Type == nil { return "" } // Parameters are always passed via the stack. if n.Op == ONAME && (n.Class() == PPARAM || n.Class() == PPARAMOUT) { return "" } if n.Type.Width > maxStackVarSize { return "too large for stack" } if (n.Op == ONEW || n.Op == OPTRLIT) && n.Type.Elem().Width >= maxImplicitStackVarSize { return "too large for stack" } if n.Op == OCLOSURE && closureType(n).Size() >= maxImplicitStackVarSize { return "too large for stack" } if n.Op == OCALLPART && partialCallType(n).Size() >= maxImplicitStackVarSize { return "too large for stack" } if n.Op == 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 *Node) { switch n.Op { default: // Unexpected Op, probably due to a previous type error. Ignore. case ODEREF, ODOTPTR: // Nothing to do. case 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() == 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() != PPARAM && n.Class() != PPARAMOUT && n.Class() != 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.Func.Closure != nil && Curfn.Op == OCLOSURE { Curfn = Curfn.Func.Closure } ln := lineno lineno = Curfn.Pos moveToHeap(n) Curfn = oldfn lineno = 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 ODOT, OINDEX, OPAREN, 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 *Node) { if Debug.r != 0 { Dump("MOVE", n) } if compiling_runtime { yyerror("%v escapes to heap, not allowed in runtime", n) } if n.Class() == PAUTOHEAP { Dump("n", n) 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.Sym = lookup("&" + n.Sym.Name) heapaddr.Orig.Sym = heapaddr.Sym heapaddr.Pos = 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() == PPARAM || n.Class() == PPARAMOUT { if n.Xoffset == BADWIDTH { 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.Type = n.Type stackcopy.Xoffset = n.Xoffset stackcopy.SetClass(n.Class()) stackcopy.Name.Param.Heapaddr = heapaddr if n.Class() == 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() == PAUTO { break } } if !found { 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(PAUTOHEAP) n.Xoffset = 0 n.Name.Param.Heapaddr = heapaddr n.Esc = EscHeap if Debug.m != 0 { Warnl(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 *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.Nbody.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 Debug.m != 0 { Warnl(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&Noescape != 0 { if Debug.m != 0 && f.Sym != nil { Warnl(f.Pos, "%v does not escape", name()) } } else { if Debug.m != 0 && f.Sym != nil { Warnl(f.Pos, "leaking param: %v", name()) } esc.AddHeap(0) } return esc.Encode() } if fn.Func.Pragma&UintptrEscapes != 0 { if f.Type.IsUintptr() { if Debug.m != 0 { Warnl(f.Pos, "marking %v as escaping uintptr", name()) } return uintptrEscapesTag } if f.IsDDD() && f.Type.Elem().IsUintptr() { // final argument is ...uintptr. if Debug.m != 0 { Warnl(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 := asNode(f.Nname) loc := e.oldLoc(n) esc := loc.paramEsc esc.Optimize() if Debug.m != 0 && !loc.escapes { if esc.Empty() { Warnl(f.Pos, "%v does not escape", name()) } if x := esc.Heap(); x >= 0 { if x == 0 { Warnl(f.Pos, "leaking param: %v", name()) } else { // TODO(mdempsky): Mention level=x like below? Warnl(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 Warnl(f.Pos, "leaking param: %v to result %v level=%d", name(), res, x) } } } return esc.Encode() }