mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
gc debug flags are currently stored in a 256-long array, that is then addressed using the ASCII numeric value of the flag itself (a quirk inherited from the old C compiler). It is also a little wasteful, since we only define 16 flags, and the other 240 array elements are always empty. This change makes Debug a struct, which also provides static checking that we're not referencing flags that does not exist. Change-Id: I2f0dfef2529325514b3398cf78635543cdf48fe0 Reviewed-on: https://go-review.googlesource.com/c/go/+/263539 Trust: Alberto Donizetti <alb.donizetti@gmail.com> Run-TryBot: Alberto Donizetti <alb.donizetti@gmail.com> TryBot-Result: Go Bot <gobot@golang.org> Reviewed-by: Matthew Dempsky <mdempsky@google.com>
472 lines
13 KiB
Go
472 lines
13 KiB
Go
// 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()
|
|
}
|