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 (
// 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
// 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 {
return int((esc>>escReturnShift(i))&bitsMaskForTag) - 1
func (l EscLeaks) Result(i int) int {
return int((l>>escReturnShift(i))&bitsMaskForTag) - 1
}
func setEscReturn(esc uint16, i, v int) uint16 {
if v < -1 {
Fatalf("invalid esc return value: %v", v)
func (l *EscLeaks) AddResult(i, derefs int) {
if *l == EscHeap {
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)
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
}
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