cmd/compile/internal/gc: handle array slice self-assign in esc.go

Instead of skipping all OSLICEARR, skip only ones with non-pointer
array type. For pointers to arrays, it's safe to apply the
self-assignment slicing optimizations.

Refactored the matching code into separate function for readability.

This is an extension to already existing optimization.

On its own, it does not improve any code under std, but
it opens some new optimization opportunities. One
of them is described in the referenced issue.

Updates #7921

Change-Id: I08ac660d3ef80eb15fd7933fb73cf53ded9333ad
Reviewed-on: https://go-review.googlesource.com/133375
Run-TryBot: Iskander Sharipov <iskander.sharipov@intel.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 254427b..cd85a38 100644
--- a/src/cmd/compile/internal/gc/esc.go
+++ b/src/cmd/compile/internal/gc/esc.go
@@ -654,9 +654,67 @@
 	}
 }
 
+func (e *EscState) isSliceSelfAssign(dst, src *Node) bool {
+	// Detect the following special case.
+	//
+	//	func (b *Buffer) Foo() {
+	//		n, m := ...
+	//		b.buf = b.buf[n:m]
+	//	}
+	//
+	// This assignment is a no-op for escape analysis,
+	// it does not store any new pointers into b that were not already there.
+	// However, without this special case b will escape, because we assign to OIND/ODOTPTR.
+	// Here we assume that the statement will not contain calls,
+	// that is, that order will move any calls to init.
+	// Otherwise base ONAME value could change between the moments
+	// when we evaluate it for dst and for src.
+
+	// dst is ONAME dereference.
+	if dst.Op != OIND && dst.Op != ODOTPTR || dst.Left.Op != ONAME {
+		return false
+	}
+	// src is a slice operation.
+	switch src.Op {
+	case OSLICE, OSLICE3, OSLICESTR:
+		// OK.
+	case OSLICEARR, OSLICE3ARR:
+		// Since arrays are embedded into containing object,
+		// slice of non-pointer array will introduce a new pointer into b that was not already there
+		// (pointer to b itself). After such assignment, if b contents escape,
+		// b escapes as well. If we ignore such OSLICEARR, we will conclude
+		// that b does not escape when b contents do.
+		//
+		// Pointer to an array is OK since it's not stored inside b directly.
+		// For slicing an array (not pointer to array), there is an implicit OADDR.
+		// We check that to determine non-pointer array slicing.
+		if src.Left.Op == OADDR {
+			return false
+		}
+	default:
+		return false
+	}
+	// slice is applied to ONAME dereference.
+	if src.Left.Op != OIND && src.Left.Op != ODOTPTR || src.Left.Left.Op != ONAME {
+		return false
+	}
+	// dst and src reference the same base ONAME.
+	return dst.Left == src.Left.Left
+}
+
 // isSelfAssign reports whether assignment from src to dst can
 // be ignored by the escape analysis as it's effectively a self-assignment.
 func (e *EscState) isSelfAssign(dst, src *Node) bool {
+	// Detect trivial assignments that assign back to the same object.
+	//
+	// It covers these cases:
+	//	val.x = val.y
+	//	val.x[i] = val.y[j]
+	//	val.x1.x2 = val.x1.y2
+	//	... etc
+	//
+	// These assignments do not change assigned object lifetime.
+
 	if dst == nil || src == nil || dst.Op != src.Op {
 		return false
 	}
@@ -830,48 +888,15 @@
 			}
 		}
 
-	// Filter out the following special case.
-	//
-	//	func (b *Buffer) Foo() {
-	//		n, m := ...
-	//		b.buf = b.buf[n:m]
-	//	}
-	//
-	// This assignment is a no-op for escape analysis,
-	// it does not store any new pointers into b that were not already there.
-	// However, without this special case b will escape, because we assign to OIND/ODOTPTR.
 	case OAS, OASOP:
-		if (n.Left.Op == OIND || n.Left.Op == ODOTPTR) && n.Left.Left.Op == ONAME && // dst is ONAME dereference
-			(n.Right.Op == OSLICE || n.Right.Op == OSLICE3 || n.Right.Op == OSLICESTR) && // src is slice operation
-			(n.Right.Left.Op == OIND || n.Right.Left.Op == ODOTPTR) && n.Right.Left.Left.Op == ONAME && // slice is applied to ONAME dereference
-			n.Left.Left == n.Right.Left.Left { // dst and src reference the same base ONAME
-
-			// Here we also assume that the statement will not contain calls,
-			// that is, that order will move any calls to init.
-			// Otherwise base ONAME value could change between the moments
-			// when we evaluate it for dst and for src.
-			//
-			// Note, this optimization does not apply to OSLICEARR,
-			// because it does introduce a new pointer into b that was not already there
-			// (pointer to b itself). After such assignment, if b contents escape,
-			// b escapes as well. If we ignore such OSLICEARR, we will conclude
-			// that b does not escape when b contents do.
+		// Filter out some no-op assignments for escape analysis.
+		if e.isSliceSelfAssign(n.Left, n.Right) {
 			if Debug['m'] != 0 {
 				Warnl(n.Pos, "%v ignoring self-assignment to %S", e.curfnSym(n), n.Left)
 			}
 
 			break
 		}
-
-		// Also skip trivial assignments that assign back to the same object.
-		//
-		// It covers these cases:
-		//	val.x = val.y
-		//	val.x[i] = val.y[j]
-		//	val.x1.x2 = val.x1.y2
-		//	... etc
-		//
-		// These assignments do not change assigned object lifetime.
 		if e.isSelfAssign(n.Left, n.Right) {
 			if Debug['m'] != 0 {
 				Warnl(n.Pos, "%v ignoring self-assignment in %S", e.curfnSym(n), n)
diff --git a/test/escape2.go b/test/escape2.go
index ef3d6a8..5c4c803 100644
--- a/test/escape2.go
+++ b/test/escape2.go
@@ -1593,11 +1593,12 @@
 // self-assignments
 
 type Buffer struct {
-	arr  [64]byte
-	buf1 []byte
-	buf2 []byte
-	str1 string
-	str2 string
+	arr    [64]byte
+	arrPtr *[64]byte
+	buf1   []byte
+	buf2   []byte
+	str1   string
+	str2   string
 }
 
 func (b *Buffer) foo() { // ERROR "\(\*Buffer\).foo b does not escape$"
@@ -1611,6 +1612,11 @@
 	b.buf1 = b.arr[1:2] // ERROR "b.arr escapes to heap$"
 }
 
+func (b *Buffer) arrayPtr() { // ERROR "\(\*Buffer\).arrayPtr b does not escape"
+	b.buf1 = b.arrPtr[1:2]   // ERROR "\(\*Buffer\).arrayPtr ignoring self-assignment to b.buf1"
+	b.buf1 = b.arrPtr[1:2:3] // ERROR "\(\*Buffer\).arrayPtr ignoring self-assignment to b.buf1"
+}
+
 func (b *Buffer) baz() { // ERROR "\(\*Buffer\).baz b does not escape$"
 	b.str1 = b.str1[1:2] // ERROR "\(\*Buffer\).baz ignoring self-assignment to b.str1$"
 	b.str1 = b.str2[1:2] // ERROR "\(\*Buffer\).baz ignoring self-assignment to b.str1$"
diff --git a/test/escape2n.go b/test/escape2n.go
index b1130d3..4b1ca1e 100644
--- a/test/escape2n.go
+++ b/test/escape2n.go
@@ -1593,11 +1593,12 @@
 // self-assignments
 
 type Buffer struct {
-	arr  [64]byte
-	buf1 []byte
-	buf2 []byte
-	str1 string
-	str2 string
+	arr    [64]byte
+	arrPtr *[64]byte
+	buf1   []byte
+	buf2   []byte
+	str1   string
+	str2   string
 }
 
 func (b *Buffer) foo() { // ERROR "\(\*Buffer\).foo b does not escape$"
@@ -1611,6 +1612,11 @@
 	b.buf1 = b.arr[1:2] // ERROR "b.arr escapes to heap$"
 }
 
+func (b *Buffer) arrayPtr() { // ERROR "\(\*Buffer\).arrayPtr b does not escape"
+	b.buf1 = b.arrPtr[1:2]   // ERROR "\(\*Buffer\).arrayPtr ignoring self-assignment to b.buf1"
+	b.buf1 = b.arrPtr[1:2:3] // ERROR "\(\*Buffer\).arrayPtr ignoring self-assignment to b.buf1"
+}
+
 func (b *Buffer) baz() { // ERROR "\(\*Buffer\).baz b does not escape$"
 	b.str1 = b.str1[1:2] // ERROR "\(\*Buffer\).baz ignoring self-assignment to b.str1$"
 	b.str1 = b.str2[1:2] // ERROR "\(\*Buffer\).baz ignoring self-assignment to b.str1$"