Commit 05a805a6 authored by Matthew Dempsky's avatar Matthew Dempsky

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: default avatarCherry Zhang <cherryyz@google.com>
parent 1c8e6077
...@@ -59,9 +59,9 @@ const ( ...@@ -59,9 +59,9 @@ const (
// something whose address is returned -- but that implies stored into the heap, // something whose address is returned -- but that implies stored into the heap,
// hence EscHeap, which means that the details are not currently relevant. ) // hence EscHeap, which means that the details are not currently relevant. )
const ( const (
bitsPerOutputInTag = 3 // For each output, the number of bits for a tag 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. 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. 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.
...@@ -210,7 +210,7 @@ func mustHeapAlloc(n *Node) bool { ...@@ -210,7 +210,7 @@ func mustHeapAlloc(n *Node) bool {
var tags [1 << (bitsPerOutputInTag + EscReturnBits)]string var tags [1 << (bitsPerOutputInTag + EscReturnBits)]string
// mktag returns the string representation for an escape analysis tag. // mktag returns the string representation for an escape analysis tag.
func mktag(mask int) string { func mktag(mask EscLeaks) string {
switch mask & EscMask { switch mask & EscMask {
case EscHeap: case EscHeap:
return "" return ""
...@@ -219,24 +219,24 @@ func mktag(mask int) string { ...@@ -219,24 +219,24 @@ func mktag(mask int) string {
Fatalf("escape mktag") Fatalf("escape mktag")
} }
if mask < len(tags) && tags[mask] != "" { if int(mask) < len(tags) && tags[mask] != "" {
return tags[mask] return tags[mask]
} }
s := fmt.Sprintf("esc:0x%x", mask) s := fmt.Sprintf("esc:0x%x", mask)
if mask < len(tags) { if int(mask) < len(tags) {
tags[mask] = s tags[mask] = s
} }
return s return s
} }
// parsetag decodes an escape analysis tag and returns the esc value. // 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:") { if !strings.HasPrefix(note, "esc:") {
return EscUnknown return EscUnknown
} }
n, _ := strconv.ParseInt(note[4:], 0, 0) n, _ := strconv.ParseInt(note[4:], 0, 0)
em := uint16(n) em := EscLeaks(n)
if em == 0 { if em == 0 {
return EscNone return EscNone
} }
...@@ -431,19 +431,22 @@ func (e *Escape) paramTag(fn *Node, narg int, f *types.Field) string { ...@@ -431,19 +431,22 @@ func (e *Escape) paramTag(fn *Node, narg int, f *types.Field) string {
return "" return ""
} }
var esc EscLeaks
// External functions are assumed unsafe, unless // External functions are assumed unsafe, unless
// //go:noescape is given before the declaration. // //go:noescape is given before the declaration.
if fn.Noescape() { if fn.Noescape() {
if Debug['m'] != 0 && f.Sym != nil { if Debug['m'] != 0 && f.Sym != nil {
Warnl(f.Pos, "%v does not escape", name()) 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 { return esc.Encode()
Warnl(f.Pos, "leaking param: %v", name())
}
return mktag(EscHeap)
} }
if fn.Func.Pragma&UintptrEscapes != 0 { if fn.Func.Pragma&UintptrEscapes != 0 {
...@@ -468,30 +471,37 @@ func (e *Escape) paramTag(fn *Node, narg int, f *types.Field) string { ...@@ -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. // Unnamed parameters are unused and therefore do not escape.
if f.Sym == nil || f.Sym.IsBlank() { if f.Sym == nil || f.Sym.IsBlank() {
return mktag(EscNone) var esc EscLeaks
return esc.Encode()
} }
n := asNode(f.Nname) n := asNode(f.Nname)
loc := e.oldLoc(n) loc := e.oldLoc(n)
esc := finalizeEsc(loc.paramEsc) esc := loc.paramEsc
esc.Optimize()
if Debug['m'] != 0 && !loc.escapes { if Debug['m'] != 0 && !loc.escapes {
if esc == EscNone { leaks := false
Warnl(f.Pos, "%v does not escape", name()) if x := esc.Heap(); x >= 0 {
} else if esc == EscHeap { if x == 0 {
Warnl(f.Pos, "leaking param: %v", name()) Warnl(f.Pos, "leaking param: %v", name())
} else { } else {
if esc&EscContentEscapes != 0 { // TODO(mdempsky): Mention level=x like below?
Warnl(f.Pos, "leaking param content: %v", name()) Warnl(f.Pos, "leaking param content: %v", name())
} }
for i := 0; i < numEscReturns; i++ { leaks = true
if x := getEscReturn(esc, i); x >= 0 { }
res := fn.Type.Results().Field(i).Sym for i := 0; i < numEscResults; i++ {
Warnl(f.Pos, "leaking param: %v to result %v level=%d", name(), res, x) 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 { ...@@ -119,9 +119,8 @@ type EscLocation struct {
// its storage can be immediately reused. // its storage can be immediately reused.
transient bool transient bool
// paramEsc records the represented parameter's escape tags. // paramEsc records the represented parameter's leak set.
// See "Parameter tags" below for details. paramEsc EscLeaks
paramEsc uint16
} }
// An EscEdge represents an assignment edge between two Go variables. // 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 ...@@ -892,20 +891,16 @@ func (e *Escape) tagHole(ks []EscHole, param *types.Field, static bool) EscHole
return e.heapHole() return e.heapHole()
} }
esc := parsetag(param.Note)
switch esc {
case EscHeap, EscUnknown:
return e.heapHole()
}
var tagKs []EscHole 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 { if ks != nil {
for i := 0; i < numEscReturns; i++ { for i := 0; i < numEscResults; i++ {
if x := getEscReturn(esc, i); x >= 0 { if x := esc.Result(i); x >= 0 {
tagKs = append(tagKs, ks[i].shift(x)) tagKs = append(tagKs, ks[i].shift(x))
} }
} }
...@@ -1247,31 +1242,20 @@ func containsClosure(f, c *Node) bool { ...@@ -1247,31 +1242,20 @@ func containsClosure(f, c *Node) bool {
// leak records that parameter l leaks to sink. // leak records that parameter l leaks to sink.
func (l *EscLocation) leakTo(sink *EscLocation, derefs int) { 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 // If sink is a result parameter and we can fit return bits
// into the escape analysis tag, then record a return leak. // into the escape analysis tag, then record a return leak.
if sink.isName(PPARAMOUT) && sink.curfn == l.curfn { if sink.isName(PPARAMOUT) && sink.curfn == l.curfn {
// TODO(mdempsky): Eliminate dependency on Vargen here. // TODO(mdempsky): Eliminate dependency on Vargen here.
ri := int(sink.n.Name.Vargen) - 1 ri := int(sink.n.Name.Vargen) - 1
if ri < numEscReturns { if ri < numEscResults {
// Leak to result parameter. // Leak to result parameter.
if old := getEscReturn(l.paramEsc, ri); old < 0 || derefs < old { l.paramEsc.AddResult(ri, derefs)
l.paramEsc = setEscReturn(l.paramEsc, ri, derefs)
}
return return
} }
} }
// Otherwise, record as heap leak. // Otherwise, record as heap leak.
if derefs > 0 { l.paramEsc.AddHeap(derefs)
l.paramEsc |= EscContentEscapes
} else {
l.paramEsc = EscHeap
}
} }
func (e *Escape) finish(fns []*Node) { func (e *Escape) finish(fns []*Node) {
...@@ -1321,37 +1305,11 @@ func (l *EscLocation) isName(c Class) bool { ...@@ -1321,37 +1305,11 @@ 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
} }
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. // Parameter tags.
// //
// The escape bits saved for each analyzed parameter record the // The escape bits saved for each analyzed parameter record the
// minimal derefs (if any) from that parameter to the heap, or to any // 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 // Paths to the heap are encoded via EscHeap (length 0) or
// EscContentEscapes (length 1); if neither of these are set, then // EscContentEscapes (length 1); if neither of these are set, then
...@@ -1365,29 +1323,98 @@ func optimizeReturns(esc uint16) uint16 { ...@@ -1365,29 +1323,98 @@ func optimizeReturns(esc uint16) uint16 {
// uintptrEscapesTag and unsafeUintptrTag). These could be simplified // uintptrEscapesTag and unsafeUintptrTag). These could be simplified
// once compatibility with esc.go is no longer a concern. // once compatibility with esc.go is no longer a concern.
const numEscReturns = (16 - EscReturnBits) / bitsPerOutputInTag const numEscResults = (16 - EscReturnBits) / bitsPerOutputInTag
// 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 (l *EscLeaks) AddHeap(derefs int) {
if *l == EscHeap {
return // already leaks to heap
}
if derefs > 0 {
*l |= EscContentEscapes
} else {
*l = EscHeap
}
}
func getEscReturn(esc uint16, i int) int { func (l EscLeaks) Result(i int) int {
return int((esc>>escReturnShift(i))&bitsMaskForTag) - 1 return int((l>>escReturnShift(i))&bitsMaskForTag) - 1
} }
func setEscReturn(esc uint16, i, v int) uint16 { func (l *EscLeaks) AddResult(i, derefs int) {
if v < -1 { if *l == EscHeap {
Fatalf("invalid esc return value: %v", v) return // already leaks to heap
} }
if v > maxEncodedLevel {
v = maxEncodedLevel 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) shift := escReturnShift(i)
esc &^= bitsMaskForTag << shift *l &^= bitsMaskForTag << shift
esc |= uint16(v+1) << shift *l |= EscLeaks(derefs+1) << shift
return esc
} }
func escReturnShift(i int) uint { func escReturnShift(i int) uint {
if uint(i) >= numEscReturns { if uint(i) >= numEscResults {
Fatalf("esc return index out of bounds: %v", i) Fatalf("esc return index out of bounds: %v", i)
} }
return uint(EscReturnBits + i*bitsPerOutputInTag) 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
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment