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>
diff --git a/src/cmd/compile/internal/gc/esc.go b/src/cmd/compile/internal/gc/esc.go
index 301fa7a..70763f2 100644
--- a/src/cmd/compile/internal/gc/esc.go
+++ b/src/cmd/compile/internal/gc/esc.go
@@ -59,9 +59,9 @@
// 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 @@
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 @@
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 @@
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 @@
// 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()
}
diff --git a/src/cmd/compile/internal/gc/escape.go b/src/cmd/compile/internal/gc/escape.go
index ebe5403..3218fae 100644
--- a/src/cmd/compile/internal/gc/escape.go
+++ b/src/cmd/compile/internal/gc/escape.go
@@ -119,9 +119,8 @@
// 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 @@
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 @@
// 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 @@
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 @@
// 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
+}