diff --git a/src/cmd/compile/internal/gc/esc.go b/src/cmd/compile/internal/gc/esc.go index 254427be4f..cd85a38eb6 100644 --- a/src/cmd/compile/internal/gc/esc.go +++ b/src/cmd/compile/internal/gc/esc.go @@ -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 // 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 @@ 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: - 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 ef3d6a88bf..5c4c803249 100644 --- a/test/escape2.go +++ b/test/escape2.go @@ -1593,11 +1593,12 @@ func ptrlitEscape() { // 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 @@ func (b *Buffer) bar() { // ERROR "leaking param: b$" 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 b1130d3c3c..4b1ca1eab8 100644 --- a/test/escape2n.go +++ b/test/escape2n.go @@ -1593,11 +1593,12 @@ func ptrlitEscape() { // 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 @@ func (b *Buffer) bar() { // ERROR "leaking param: b$" 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$"