mirror of https://github.com/golang/go.git
cmd/compile: introduce EscLeaks abstraction
This CL better abstracts away the parameter leak info that was directly encoded into the uint16 value. Followup CL will rewrite the implementation. Passes toolstash-check. Updates #33981. Change-Id: I27f81d26f5dd2d85f5b0e5250ca529819a1f11c2 Reviewed-on: https://go-review.googlesource.com/c/go/+/197679 Run-TryBot: Matthew Dempsky <mdempsky@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Cherry Zhang <cherryyz@google.com>
This commit is contained in:
parent
1c8e6077f6
commit
05a805a6de
|
|
@ -59,9 +59,9 @@ const (
|
|||
// 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 = uint16(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.
|
||||
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.
|
||||
|
|
@ -210,7 +210,7 @@ func mustHeapAlloc(n *Node) bool {
|
|||
var tags [1 << (bitsPerOutputInTag + EscReturnBits)]string
|
||||
|
||||
// mktag returns the string representation for an escape analysis tag.
|
||||
func mktag(mask int) string {
|
||||
func mktag(mask EscLeaks) string {
|
||||
switch mask & EscMask {
|
||||
case EscHeap:
|
||||
return ""
|
||||
|
|
@ -219,24 +219,24 @@ func mktag(mask int) string {
|
|||
Fatalf("escape mktag")
|
||||
}
|
||||
|
||||
if mask < len(tags) && tags[mask] != "" {
|
||||
if int(mask) < len(tags) && tags[mask] != "" {
|
||||
return tags[mask]
|
||||
}
|
||||
|
||||
s := fmt.Sprintf("esc:0x%x", mask)
|
||||
if mask < len(tags) {
|
||||
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) uint16 {
|
||||
func parsetag(note string) EscLeaks {
|
||||
if !strings.HasPrefix(note, "esc:") {
|
||||
return EscUnknown
|
||||
}
|
||||
n, _ := strconv.ParseInt(note[4:], 0, 0)
|
||||
em := uint16(n)
|
||||
em := EscLeaks(n)
|
||||
if em == 0 {
|
||||
return EscNone
|
||||
}
|
||||
|
|
@ -431,19 +431,22 @@ func (e *Escape) paramTag(fn *Node, narg int, f *types.Field) string {
|
|||
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())
|
||||
}
|
||||
return mktag(EscNone)
|
||||
} else {
|
||||
if Debug['m'] != 0 && f.Sym != nil {
|
||||
Warnl(f.Pos, "leaking param: %v", name())
|
||||
}
|
||||
esc.AddHeap(0)
|
||||
}
|
||||
|
||||
if Debug['m'] != 0 && f.Sym != nil {
|
||||
Warnl(f.Pos, "leaking param: %v", name())
|
||||
}
|
||||
return mktag(EscHeap)
|
||||
return esc.Encode()
|
||||
}
|
||||
|
||||
if fn.Func.Pragma&UintptrEscapes != 0 {
|
||||
|
|
@ -468,30 +471,37 @@ func (e *Escape) paramTag(fn *Node, narg int, f *types.Field) string {
|
|||
|
||||
// Unnamed parameters are unused and therefore do not escape.
|
||||
if f.Sym == nil || f.Sym.IsBlank() {
|
||||
return mktag(EscNone)
|
||||
var esc EscLeaks
|
||||
return esc.Encode()
|
||||
}
|
||||
|
||||
n := asNode(f.Nname)
|
||||
loc := e.oldLoc(n)
|
||||
esc := finalizeEsc(loc.paramEsc)
|
||||
esc := loc.paramEsc
|
||||
esc.Optimize()
|
||||
|
||||
if Debug['m'] != 0 && !loc.escapes {
|
||||
if esc == EscNone {
|
||||
Warnl(f.Pos, "%v does not escape", name())
|
||||
} else if esc == EscHeap {
|
||||
Warnl(f.Pos, "leaking param: %v", name())
|
||||
} else {
|
||||
if esc&EscContentEscapes != 0 {
|
||||
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())
|
||||
}
|
||||
for i := 0; i < numEscReturns; i++ {
|
||||
if x := getEscReturn(esc, 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
|
||||
}
|
||||
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 mktag(int(esc))
|
||||
return esc.Encode()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -119,9 +119,8 @@ type EscLocation struct {
|
|||
// its storage can be immediately reused.
|
||||
transient bool
|
||||
|
||||
// paramEsc records the represented parameter's escape tags.
|
||||
// See "Parameter tags" below for details.
|
||||
paramEsc uint16
|
||||
// paramEsc records the represented parameter's leak set.
|
||||
paramEsc EscLeaks
|
||||
}
|
||||
|
||||
// An EscEdge represents an assignment edge between two Go variables.
|
||||
|
|
@ -892,20 +891,16 @@ func (e *Escape) tagHole(ks []EscHole, param *types.Field, static bool) EscHole
|
|||
return e.heapHole()
|
||||
}
|
||||
|
||||
esc := parsetag(param.Note)
|
||||
switch esc {
|
||||
case EscHeap, EscUnknown:
|
||||
return e.heapHole()
|
||||
}
|
||||
|
||||
var tagKs []EscHole
|
||||
if esc&EscContentEscapes != 0 {
|
||||
tagKs = append(tagKs, e.heapHole().shift(1))
|
||||
|
||||
esc := ParseLeaks(param.Note)
|
||||
if x := esc.Heap(); x >= 0 {
|
||||
tagKs = append(tagKs, e.heapHole().shift(x))
|
||||
}
|
||||
|
||||
if ks != nil {
|
||||
for i := 0; i < numEscReturns; i++ {
|
||||
if x := getEscReturn(esc, i); x >= 0 {
|
||||
for i := 0; i < numEscResults; i++ {
|
||||
if x := esc.Result(i); x >= 0 {
|
||||
tagKs = append(tagKs, ks[i].shift(x))
|
||||
}
|
||||
}
|
||||
|
|
@ -1247,31 +1242,20 @@ func containsClosure(f, c *Node) bool {
|
|||
|
||||
// leak records that parameter l leaks to sink.
|
||||
func (l *EscLocation) leakTo(sink *EscLocation, derefs int) {
|
||||
// Short circuit if l already leaks to heap.
|
||||
if l.paramEsc == EscHeap {
|
||||
return
|
||||
}
|
||||
|
||||
// If sink is a result parameter and we can fit return bits
|
||||
// into the escape analysis tag, then record a return leak.
|
||||
if sink.isName(PPARAMOUT) && sink.curfn == l.curfn {
|
||||
// TODO(mdempsky): Eliminate dependency on Vargen here.
|
||||
ri := int(sink.n.Name.Vargen) - 1
|
||||
if ri < numEscReturns {
|
||||
if ri < numEscResults {
|
||||
// Leak to result parameter.
|
||||
if old := getEscReturn(l.paramEsc, ri); old < 0 || derefs < old {
|
||||
l.paramEsc = setEscReturn(l.paramEsc, ri, derefs)
|
||||
}
|
||||
l.paramEsc.AddResult(ri, derefs)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, record as heap leak.
|
||||
if derefs > 0 {
|
||||
l.paramEsc |= EscContentEscapes
|
||||
} else {
|
||||
l.paramEsc = EscHeap
|
||||
}
|
||||
l.paramEsc.AddHeap(derefs)
|
||||
}
|
||||
|
||||
func (e *Escape) finish(fns []*Node) {
|
||||
|
|
@ -1321,37 +1305,11 @@ func (l *EscLocation) isName(c Class) bool {
|
|||
return l.n != nil && l.n.Op == ONAME && l.n.Class() == c
|
||||
}
|
||||
|
||||
func finalizeEsc(esc uint16) uint16 {
|
||||
esc = optimizeReturns(esc)
|
||||
|
||||
if esc>>EscReturnBits != 0 {
|
||||
esc |= EscReturn
|
||||
} else if esc&EscMask == 0 {
|
||||
esc |= EscNone
|
||||
}
|
||||
|
||||
return esc
|
||||
}
|
||||
|
||||
func optimizeReturns(esc uint16) uint16 {
|
||||
if esc&EscContentEscapes != 0 {
|
||||
// EscContentEscapes represents a path of length 1
|
||||
// from the heap. No point in keeping paths of equal
|
||||
// or longer length to result parameters.
|
||||
for i := 0; i < numEscReturns; i++ {
|
||||
if x := getEscReturn(esc, i); x >= 1 {
|
||||
esc = setEscReturn(esc, i, -1)
|
||||
}
|
||||
}
|
||||
}
|
||||
return esc
|
||||
}
|
||||
|
||||
// Parameter tags.
|
||||
//
|
||||
// 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 numEscReturns) result parameters.
|
||||
// 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
|
||||
|
|
@ -1365,29 +1323,98 @@ func optimizeReturns(esc uint16) uint16 {
|
|||
// uintptrEscapesTag and unsafeUintptrTag). These could be simplified
|
||||
// once compatibility with esc.go is no longer a concern.
|
||||
|
||||
const numEscReturns = (16 - EscReturnBits) / bitsPerOutputInTag
|
||||
const numEscResults = (16 - EscReturnBits) / bitsPerOutputInTag
|
||||
|
||||
func getEscReturn(esc uint16, i int) int {
|
||||
return int((esc>>escReturnShift(i))&bitsMaskForTag) - 1
|
||||
// An EscLeaks records the minimal deref count for assignment flows
|
||||
// from a parameter to the heap or to any of its function's (first
|
||||
// numEscResults) result parameters. If no assignment flow exists,
|
||||
// that respective count is reported as -1.
|
||||
type EscLeaks uint16
|
||||
|
||||
func (l EscLeaks) Heap() int {
|
||||
if l == EscHeap {
|
||||
return 0
|
||||
}
|
||||
if l&EscContentEscapes != 0 {
|
||||
return 1
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func setEscReturn(esc uint16, i, v int) uint16 {
|
||||
if v < -1 {
|
||||
Fatalf("invalid esc return value: %v", v)
|
||||
func (l *EscLeaks) AddHeap(derefs int) {
|
||||
if *l == EscHeap {
|
||||
return // already leaks to heap
|
||||
}
|
||||
if v > maxEncodedLevel {
|
||||
v = maxEncodedLevel
|
||||
|
||||
if derefs > 0 {
|
||||
*l |= EscContentEscapes
|
||||
} else {
|
||||
*l = EscHeap
|
||||
}
|
||||
}
|
||||
|
||||
func (l EscLeaks) Result(i int) int {
|
||||
return int((l>>escReturnShift(i))&bitsMaskForTag) - 1
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
if derefs > maxEncodedLevel {
|
||||
derefs = maxEncodedLevel
|
||||
}
|
||||
|
||||
shift := escReturnShift(i)
|
||||
esc &^= bitsMaskForTag << shift
|
||||
esc |= uint16(v+1) << shift
|
||||
return esc
|
||||
*l &^= bitsMaskForTag << shift
|
||||
*l |= EscLeaks(derefs+1) << shift
|
||||
}
|
||||
|
||||
func escReturnShift(i int) uint {
|
||||
if uint(i) >= numEscReturns {
|
||||
if uint(i) >= numEscResults {
|
||||
Fatalf("esc return index out of bounds: %v", i)
|
||||
}
|
||||
return uint(EscReturnBits + i*bitsPerOutputInTag)
|
||||
}
|
||||
|
||||
func (l *EscLeaks) 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l EscLeaks) Encode() string {
|
||||
if l&EscMask == 0 {
|
||||
if l>>EscReturnBits != 0 {
|
||||
l |= EscReturn
|
||||
} else {
|
||||
l |= EscNone
|
||||
}
|
||||
}
|
||||
|
||||
return mktag(l)
|
||||
}
|
||||
|
||||
func ParseLeaks(s string) EscLeaks {
|
||||
l := parsetag(s)
|
||||
if l == EscUnknown {
|
||||
return EscHeap
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue