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
+}