mirror of https://github.com/golang/go.git
cmd/compile: reimplement parameter leak encoding
Currently, escape analysis is able to record at most one dereference
when a parameter leaks to the heap; that is, at call sites, it can't
distinguish between any of these three functions:
func x1(p ****int) { sink = *p }
func x2(p ****int) { sink = **p }
func x3(p ****int) { sink = ***p }
Similarly, it's limited to recording parameter leaks to only the first
4 parameters, and only up to 6 dereferences.
All of these limitations are due to the awkward encoding scheme used
at the moment.
This CL replaces the encoding scheme with a simple [8]uint8 array,
which can handle up to the first 7 parameters, and up to 254
dereferences, which ought to be enough for anyone. And if not, it's
much more easily increased.
Shrinks export data size geometric mean for Kubernetes by 0.07%.
Fixes #33981.
Change-Id: I10a94b9accac9a0c91490e0d6d458316f5ca1e13
Reviewed-on: https://go-review.googlesource.com/c/go/+/197680
Reviewed-by: Cherry Zhang <cherryyz@google.com>
This commit is contained in:
parent
05a805a6de
commit
a0894ea5b5
|
|
@ -7,8 +7,6 @@ package gc
|
||||||
import (
|
import (
|
||||||
"cmd/compile/internal/types"
|
"cmd/compile/internal/types"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func escapes(all []*Node) {
|
func escapes(all []*Node) {
|
||||||
|
|
@ -36,32 +34,11 @@ func max8(a, b int8) int8 {
|
||||||
return b
|
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 (
|
const (
|
||||||
EscUnknown = iota
|
EscUnknown = iota
|
||||||
EscNone // Does not escape to heap, result, or parameters.
|
EscNone // Does not escape to heap, result, or parameters.
|
||||||
EscReturn // Is returned or reachable from returned.
|
EscHeap // Reachable from the heap
|
||||||
EscHeap // Reachable from the heap
|
EscNever // By construction will not escape.
|
||||||
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<<bitsPerOutputInTag) - 1 // The bit mask to extract a single tag.
|
|
||||||
maxEncodedLevel = int(bitsMaskForTag - 1) // The largest level that can be stored in a tag.
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// funcSym returns fn.Func.Nname.Sym if no nils are encountered along the way.
|
// funcSym returns fn.Func.Nname.Sym if no nils are encountered along the way.
|
||||||
|
|
@ -200,49 +177,6 @@ func mustHeapAlloc(n *Node) bool {
|
||||||
n.Op == OMAKESLICE && !isSmallMakeSlice(n))
|
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
|
// addrescapes tags node n as having had its address taken
|
||||||
// by "increasing" the "value" of n.Esc to EscHeap.
|
// by "increasing" the "value" of n.Esc to EscHeap.
|
||||||
// Storage is allocated as necessary to allow the address
|
// Storage is allocated as necessary to allow the address
|
||||||
|
|
@ -481,7 +415,9 @@ func (e *Escape) paramTag(fn *Node, narg int, f *types.Field) string {
|
||||||
esc.Optimize()
|
esc.Optimize()
|
||||||
|
|
||||||
if Debug['m'] != 0 && !loc.escapes {
|
if Debug['m'] != 0 && !loc.escapes {
|
||||||
leaks := false
|
if esc.Empty() {
|
||||||
|
Warnl(f.Pos, "%v does not escape", name())
|
||||||
|
}
|
||||||
if x := esc.Heap(); x >= 0 {
|
if x := esc.Heap(); x >= 0 {
|
||||||
if x == 0 {
|
if x == 0 {
|
||||||
Warnl(f.Pos, "leaking param: %v", name())
|
Warnl(f.Pos, "leaking param: %v", name())
|
||||||
|
|
@ -489,18 +425,13 @@ func (e *Escape) paramTag(fn *Node, narg int, f *types.Field) string {
|
||||||
// TODO(mdempsky): Mention level=x like below?
|
// TODO(mdempsky): Mention level=x like below?
|
||||||
Warnl(f.Pos, "leaking param content: %v", name())
|
Warnl(f.Pos, "leaking param content: %v", name())
|
||||||
}
|
}
|
||||||
leaks = true
|
|
||||||
}
|
}
|
||||||
for i := 0; i < numEscResults; i++ {
|
for i := 0; i < numEscResults; i++ {
|
||||||
if x := esc.Result(i); x >= 0 {
|
if x := esc.Result(i); x >= 0 {
|
||||||
res := fn.Type.Results().Field(i).Sym
|
res := fn.Type.Results().Field(i).Sym
|
||||||
Warnl(f.Pos, "leaking param: %v to result %v level=%d", name(), res, x)
|
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()
|
return esc.Encode()
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@ package gc
|
||||||
import (
|
import (
|
||||||
"cmd/compile/internal/types"
|
"cmd/compile/internal/types"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Escape analysis.
|
// Escape analysis.
|
||||||
|
|
@ -169,11 +171,7 @@ func (e *Escape) initFunc(fn *Node) {
|
||||||
// Allocate locations for local variables.
|
// Allocate locations for local variables.
|
||||||
for _, dcl := range fn.Func.Dcl {
|
for _, dcl := range fn.Func.Dcl {
|
||||||
if dcl.Op == ONAME {
|
if dcl.Op == ONAME {
|
||||||
loc := e.newLoc(dcl, false)
|
e.newLoc(dcl, false)
|
||||||
|
|
||||||
if dcl.Class() == PPARAM && fn.Nbody.Len() == 0 && !fn.Noescape() {
|
|
||||||
loc.paramEsc = EscHeap
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1305,88 +1303,56 @@ func (l *EscLocation) isName(c Class) bool {
|
||||||
return l.n != nil && l.n.Op == ONAME && l.n.Class() == c
|
return l.n != nil && l.n.Op == ONAME && l.n.Class() == c
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parameter tags.
|
const numEscResults = 7
|
||||||
//
|
|
||||||
// The escape bits saved for each analyzed parameter record the
|
|
||||||
// minimal derefs (if any) from that parameter to the heap, or to any
|
|
||||||
// of its function's (first numEscResults) result parameters.
|
|
||||||
//
|
|
||||||
// Paths to the heap are encoded via EscHeap (length 0) or
|
|
||||||
// EscContentEscapes (length 1); if neither of these are set, then
|
|
||||||
// there's no path to the heap.
|
|
||||||
//
|
|
||||||
// Paths to the result parameters are encoded in the upper
|
|
||||||
// bits.
|
|
||||||
//
|
|
||||||
// There are other values stored in the escape bits by esc.go for
|
|
||||||
// vestigial reasons, and other special tag values used (e.g.,
|
|
||||||
// uintptrEscapesTag and unsafeUintptrTag). These could be simplified
|
|
||||||
// once compatibility with esc.go is no longer a concern.
|
|
||||||
|
|
||||||
const numEscResults = (16 - EscReturnBits) / bitsPerOutputInTag
|
// An EscLeaks represents a set of assignment flows from a parameter
|
||||||
|
// to the heap or to any of its function's (first numEscResults)
|
||||||
|
// result parameters.
|
||||||
|
type EscLeaks [1 + numEscResults]uint8
|
||||||
|
|
||||||
// An EscLeaks records the minimal deref count for assignment flows
|
// Empty reports whether l is an empty set (i.e., no assignment flows).
|
||||||
// from a parameter to the heap or to any of its function's (first
|
func (l EscLeaks) Empty() bool { return l == EscLeaks{} }
|
||||||
// numEscResults) result parameters. If no assignment flow exists,
|
|
||||||
// that respective count is reported as -1.
|
|
||||||
type EscLeaks uint16
|
|
||||||
|
|
||||||
func (l EscLeaks) Heap() int {
|
// Heap returns the minimum deref count of any assignment flow from l
|
||||||
if l == EscHeap {
|
// to the heap. If no such flows exist, Heap returns -1.
|
||||||
return 0
|
func (l EscLeaks) Heap() int { return l.get(0) }
|
||||||
}
|
|
||||||
if l&EscContentEscapes != 0 {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *EscLeaks) AddHeap(derefs int) {
|
// Result returns the minimum deref count of any assignment flow from
|
||||||
if *l == EscHeap {
|
// l to its function's i'th result parameter. If no such flows exist,
|
||||||
return // already leaks to heap
|
// Result returns -1.
|
||||||
}
|
func (l EscLeaks) Result(i int) int { return l.get(1 + i) }
|
||||||
|
|
||||||
if derefs > 0 {
|
// AddHeap adds an assignment flow from l to the heap.
|
||||||
*l |= EscContentEscapes
|
func (l *EscLeaks) AddHeap(derefs int) { l.add(0, derefs) }
|
||||||
} else {
|
|
||||||
*l = EscHeap
|
// AddResult adds an assignment flow from l to its function's i'th
|
||||||
|
// result parameter.
|
||||||
|
func (l *EscLeaks) AddResult(i, derefs int) { l.add(1+i, derefs) }
|
||||||
|
|
||||||
|
func (l *EscLeaks) setResult(i, derefs int) { l.set(1+i, derefs) }
|
||||||
|
|
||||||
|
func (l EscLeaks) get(i int) int { return int(l[i]) - 1 }
|
||||||
|
|
||||||
|
func (l *EscLeaks) add(i, derefs int) {
|
||||||
|
if old := l.get(i); old < 0 || derefs < old {
|
||||||
|
l.set(i, derefs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l EscLeaks) Result(i int) int {
|
func (l *EscLeaks) set(i, derefs int) {
|
||||||
return int((l>>escReturnShift(i))&bitsMaskForTag) - 1
|
v := derefs + 1
|
||||||
}
|
if v < 0 {
|
||||||
|
|
||||||
func (l *EscLeaks) AddResult(i, derefs int) {
|
|
||||||
if *l == EscHeap {
|
|
||||||
return // already leaks to heap
|
|
||||||
}
|
|
||||||
|
|
||||||
if old := l.Result(i); old < 0 || derefs < old {
|
|
||||||
l.setResult(i, derefs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *EscLeaks) setResult(i, derefs int) {
|
|
||||||
if derefs < -1 {
|
|
||||||
Fatalf("invalid derefs count: %v", derefs)
|
Fatalf("invalid derefs count: %v", derefs)
|
||||||
}
|
}
|
||||||
if derefs > maxEncodedLevel {
|
if v > math.MaxUint8 {
|
||||||
derefs = maxEncodedLevel
|
v = math.MaxUint8
|
||||||
}
|
}
|
||||||
|
|
||||||
shift := escReturnShift(i)
|
l[i] = uint8(v)
|
||||||
*l &^= bitsMaskForTag << shift
|
|
||||||
*l |= EscLeaks(derefs+1) << shift
|
|
||||||
}
|
|
||||||
|
|
||||||
func escReturnShift(i int) uint {
|
|
||||||
if uint(i) >= numEscResults {
|
|
||||||
Fatalf("esc return index out of bounds: %v", i)
|
|
||||||
}
|
|
||||||
return uint(EscReturnBits + i*bitsPerOutputInTag)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Optimize removes result flow paths that are equal in length or
|
||||||
|
// longer than the shortest heap flow path.
|
||||||
func (l *EscLeaks) Optimize() {
|
func (l *EscLeaks) Optimize() {
|
||||||
// If we have a path to the heap, then there's no use in
|
// If we have a path to the heap, then there's no use in
|
||||||
// keeping equal or longer paths elsewhere.
|
// keeping equal or longer paths elsewhere.
|
||||||
|
|
@ -1399,22 +1365,35 @@ func (l *EscLeaks) Optimize() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var leakTagCache = map[EscLeaks]string{}
|
||||||
|
|
||||||
|
// Encode converts l into a binary string for export data.
|
||||||
func (l EscLeaks) Encode() string {
|
func (l EscLeaks) Encode() string {
|
||||||
if l&EscMask == 0 {
|
if l.Heap() == 0 {
|
||||||
if l>>EscReturnBits != 0 {
|
// Space optimization: empty string encodes more
|
||||||
l |= EscReturn
|
// efficiently in export data.
|
||||||
} else {
|
return ""
|
||||||
l |= EscNone
|
}
|
||||||
}
|
if s, ok := leakTagCache[l]; ok {
|
||||||
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
return mktag(l)
|
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 an EscLeaks.
|
||||||
func ParseLeaks(s string) EscLeaks {
|
func ParseLeaks(s string) EscLeaks {
|
||||||
l := parsetag(s)
|
var l EscLeaks
|
||||||
if l == EscUnknown {
|
if !strings.HasPrefix(s, "esc:") {
|
||||||
return EscHeap
|
l.AddHeap(0)
|
||||||
|
return l
|
||||||
}
|
}
|
||||||
|
copy(l[:], s[4:])
|
||||||
return l
|
return l
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -205,7 +205,7 @@ func param7(i ***int) { // ERROR "leaking param content: i$"
|
||||||
|
|
||||||
func caller7() {
|
func caller7() {
|
||||||
i := 0 // ERROR "moved to heap: i$"
|
i := 0 // ERROR "moved to heap: i$"
|
||||||
p := &i // ERROR "moved to heap: p$"
|
p := &i
|
||||||
p2 := &p
|
p2 := &p
|
||||||
param7(&p2)
|
param7(&p2)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue