Commit 859cf7fc authored by Iskander Sharipov's avatar Iskander Sharipov

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: default avatarCherry Zhang <cherryyz@google.com>
parent 2d82465d
...@@ -654,9 +654,67 @@ func (e *EscState) esclist(l Nodes, parent *Node) { ...@@ -654,9 +654,67 @@ func (e *EscState) esclist(l Nodes, parent *Node) {
} }
} }
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 // isSelfAssign reports whether assignment from src to dst can
// be ignored by the escape analysis as it's effectively a self-assignment. // be ignored by the escape analysis as it's effectively a self-assignment.
func (e *EscState) isSelfAssign(dst, src *Node) bool { 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 { if dst == nil || src == nil || dst.Op != src.Op {
return false return false
} }
...@@ -830,48 +888,15 @@ opSwitch: ...@@ -830,48 +888,15 @@ opSwitch:
} }
} }
// 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: case OAS, OASOP:
if (n.Left.Op == OIND || n.Left.Op == ODOTPTR) && n.Left.Left.Op == ONAME && // dst is ONAME dereference // Filter out some no-op assignments for escape analysis.
(n.Right.Op == OSLICE || n.Right.Op == OSLICE3 || n.Right.Op == OSLICESTR) && // src is slice operation if e.isSliceSelfAssign(n.Left, n.Right) {
(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.
if Debug['m'] != 0 { if Debug['m'] != 0 {
Warnl(n.Pos, "%v ignoring self-assignment to %S", e.curfnSym(n), n.Left) Warnl(n.Pos, "%v ignoring self-assignment to %S", e.curfnSym(n), n.Left)
} }
break 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 e.isSelfAssign(n.Left, n.Right) {
if Debug['m'] != 0 { if Debug['m'] != 0 {
Warnl(n.Pos, "%v ignoring self-assignment in %S", e.curfnSym(n), n) Warnl(n.Pos, "%v ignoring self-assignment in %S", e.curfnSym(n), n)
......
...@@ -1594,6 +1594,7 @@ func ptrlitEscape() { ...@@ -1594,6 +1594,7 @@ func ptrlitEscape() {
type Buffer struct { type Buffer struct {
arr [64]byte arr [64]byte
arrPtr *[64]byte
buf1 []byte buf1 []byte
buf2 []byte buf2 []byte
str1 string str1 string
...@@ -1611,6 +1612,11 @@ func (b *Buffer) bar() { // ERROR "leaking param: b$" ...@@ -1611,6 +1612,11 @@ func (b *Buffer) bar() { // ERROR "leaking param: b$"
b.buf1 = b.arr[1:2] // ERROR "b.arr escapes to heap$" 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$" 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.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$" b.str1 = b.str2[1:2] // ERROR "\(\*Buffer\).baz ignoring self-assignment to b.str1$"
......
...@@ -1594,6 +1594,7 @@ func ptrlitEscape() { ...@@ -1594,6 +1594,7 @@ func ptrlitEscape() {
type Buffer struct { type Buffer struct {
arr [64]byte arr [64]byte
arrPtr *[64]byte
buf1 []byte buf1 []byte
buf2 []byte buf2 []byte
str1 string str1 string
...@@ -1611,6 +1612,11 @@ func (b *Buffer) bar() { // ERROR "leaking param: b$" ...@@ -1611,6 +1612,11 @@ func (b *Buffer) bar() { // ERROR "leaking param: b$"
b.buf1 = b.arr[1:2] // ERROR "b.arr escapes to heap$" 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$" 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.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$" b.str1 = b.str2[1:2] // ERROR "\(\*Buffer\).baz ignoring self-assignment to b.str1$"
......
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