// 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" "strconv" "strings" ) 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 } // Escape constants are numbered in order of increasing "escapiness" // to help make inferences be monotonic. With the exception of // EscNever which is sticky, eX < eY means that eY is more exposed // than eX, and hence replaces it in a conservative analysis. const ( EscUnknown = iota EscNone // Does not escape to heap, result, or parameters. EscReturn // Is returned or reachable from returned. EscHeap // Reachable from the heap EscNever // By construction will not escape. EscBits = 3 EscMask = (1 << EscBits) - 1 EscContentEscapes = 1 << EscBits // value obtained by indirect of parameter escapes to heap EscReturnBits = EscBits + 1 // Node.esc encoding = | escapeReturnEncoding:(width-4) | contentEscapes:1 | escEnum:3 ) // For each input parameter to a function, the escapeReturnEncoding describes // how the parameter may leak to the function's outputs. This is currently the // "level" of the leak where level is 0 or larger (negative level means stored into // something whose address is returned -- but that implies stored into the heap, // hence EscHeap, which means that the details are not currently relevant. ) const ( bitsPerOutputInTag = 3 // For each output, the number of bits for a tag bitsMaskForTag = EscLeaks(1< maxStackVarSize || (n.Op == ONEW || n.Op == OPTRLIT) && n.Type.Elem().Width >= maxImplicitStackVarSize || n.Op == OMAKESLICE && !isSmallMakeSlice(n)) } // Common case for escapes is 16 bits 000000000xxxEEEE // where commonest cases for xxx encoding in-to-out pointer // flow are 000, 001, 010, 011 and EEEE is computed Esc bits. // Note width of xxx depends on value of constant // bitsPerOutputInTag -- expect 2 or 3, so in practice the // tag cache array is 64 or 128 long. Some entries will // never be populated. var tags [1 << (bitsPerOutputInTag + EscReturnBits)]string // mktag returns the string representation for an escape analysis tag. func mktag(mask EscLeaks) string { switch mask & EscMask { case EscHeap: return "" case EscNone, EscReturn: default: Fatalf("escape mktag") } if int(mask) < len(tags) && tags[mask] != "" { return tags[mask] } s := fmt.Sprintf("esc:0x%x", mask) if int(mask) < len(tags) { tags[mask] = s } return s } // parsetag decodes an escape analysis tag and returns the esc value. func parsetag(note string) EscLeaks { if !strings.HasPrefix(note, "esc:") { return EscUnknown } n, _ := strconv.ParseInt(note[4:], 0, 0) em := EscLeaks(n) if em == 0 { return EscNone } return em } // 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.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.SetAddable(false) 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.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.Etype == TUINTPTR { if Debug['m'] != 0 { Warnl(f.Pos, "assuming %v is unsafe uintptr", name()) } return unsafeUintptrTag } if !types.Haspointers(f.Type) { // 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.Noescape() { 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.Etype == TUINTPTR { if Debug['m'] != 0 { Warnl(f.Pos, "marking %v as escaping uintptr", name()) } return uintptrEscapesTag } if f.IsDDD() && f.Type.Elem().Etype == TUINTPTR { // final argument is ...uintptr. if Debug['m'] != 0 { Warnl(f.Pos, "marking %v as escaping ...uintptr", name()) } return uintptrEscapesTag } } if !types.Haspointers(f.Type) { // 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 { leaks := false 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()) } leaks = true } 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) leaks = true } } if !leaks { Warnl(f.Pos, "%v does not escape", name()) } } return esc.Encode() }