]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/compile: introduce EscLeaks abstraction
authorMatthew Dempsky <mdempsky@google.com>
Thu, 26 Sep 2019 22:55:58 +0000 (15:55 -0700)
committerMatthew Dempsky <mdempsky@google.com>
Mon, 7 Oct 2019 18:49:53 +0000 (18:49 +0000)
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>
src/cmd/compile/internal/gc/esc.go
src/cmd/compile/internal/gc/escape.go

index 301fa7a8fcfac23afdb4582711e0bb56c81b519d..70763f242c355ed4ec62c89cf4dd80cbaf88ee7f 100644 (file)
@@ -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()
 }
index ebe54031866d2d436fc1882ca6417c807c2024d5..3218fae5a2701e34923e0d8937a6030505dc43b3 100644 (file)
@@ -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
+}