Skip to content

Commit 859cf7f

Browse files
author
Iskander Sharipov
committed
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>
1 parent 2d82465 commit 859cf7f

File tree

3 files changed

+82
-45
lines changed

3 files changed

+82
-45
lines changed

Diff for: src/cmd/compile/internal/gc/esc.go

+60-35
Original file line numberDiff line numberDiff line change
@@ -654,9 +654,67 @@ func (e *EscState) esclist(l Nodes, parent *Node) {
654654
}
655655
}
656656

657+
func (e *EscState) isSliceSelfAssign(dst, src *Node) bool {
658+
// Detect the following special case.
659+
//
660+
// func (b *Buffer) Foo() {
661+
// n, m := ...
662+
// b.buf = b.buf[n:m]
663+
// }
664+
//
665+
// This assignment is a no-op for escape analysis,
666+
// it does not store any new pointers into b that were not already there.
667+
// However, without this special case b will escape, because we assign to OIND/ODOTPTR.
668+
// Here we assume that the statement will not contain calls,
669+
// that is, that order will move any calls to init.
670+
// Otherwise base ONAME value could change between the moments
671+
// when we evaluate it for dst and for src.
672+
673+
// dst is ONAME dereference.
674+
if dst.Op != OIND && dst.Op != ODOTPTR || dst.Left.Op != ONAME {
675+
return false
676+
}
677+
// src is a slice operation.
678+
switch src.Op {
679+
case OSLICE, OSLICE3, OSLICESTR:
680+
// OK.
681+
case OSLICEARR, OSLICE3ARR:
682+
// Since arrays are embedded into containing object,
683+
// slice of non-pointer array will introduce a new pointer into b that was not already there
684+
// (pointer to b itself). After such assignment, if b contents escape,
685+
// b escapes as well. If we ignore such OSLICEARR, we will conclude
686+
// that b does not escape when b contents do.
687+
//
688+
// Pointer to an array is OK since it's not stored inside b directly.
689+
// For slicing an array (not pointer to array), there is an implicit OADDR.
690+
// We check that to determine non-pointer array slicing.
691+
if src.Left.Op == OADDR {
692+
return false
693+
}
694+
default:
695+
return false
696+
}
697+
// slice is applied to ONAME dereference.
698+
if src.Left.Op != OIND && src.Left.Op != ODOTPTR || src.Left.Left.Op != ONAME {
699+
return false
700+
}
701+
// dst and src reference the same base ONAME.
702+
return dst.Left == src.Left.Left
703+
}
704+
657705
// isSelfAssign reports whether assignment from src to dst can
658706
// be ignored by the escape analysis as it's effectively a self-assignment.
659707
func (e *EscState) isSelfAssign(dst, src *Node) bool {
708+
// Detect trivial assignments that assign back to the same object.
709+
//
710+
// It covers these cases:
711+
// val.x = val.y
712+
// val.x[i] = val.y[j]
713+
// val.x1.x2 = val.x1.y2
714+
// ... etc
715+
//
716+
// These assignments do not change assigned object lifetime.
717+
660718
if dst == nil || src == nil || dst.Op != src.Op {
661719
return false
662720
}
@@ -830,48 +888,15 @@ opSwitch:
830888
}
831889
}
832890

833-
// Filter out the following special case.
834-
//
835-
// func (b *Buffer) Foo() {
836-
// n, m := ...
837-
// b.buf = b.buf[n:m]
838-
// }
839-
//
840-
// This assignment is a no-op for escape analysis,
841-
// it does not store any new pointers into b that were not already there.
842-
// However, without this special case b will escape, because we assign to OIND/ODOTPTR.
843891
case OAS, OASOP:
844-
if (n.Left.Op == OIND || n.Left.Op == ODOTPTR) && n.Left.Left.Op == ONAME && // dst is ONAME dereference
845-
(n.Right.Op == OSLICE || n.Right.Op == OSLICE3 || n.Right.Op == OSLICESTR) && // src is slice operation
846-
(n.Right.Left.Op == OIND || n.Right.Left.Op == ODOTPTR) && n.Right.Left.Left.Op == ONAME && // slice is applied to ONAME dereference
847-
n.Left.Left == n.Right.Left.Left { // dst and src reference the same base ONAME
848-
849-
// Here we also assume that the statement will not contain calls,
850-
// that is, that order will move any calls to init.
851-
// Otherwise base ONAME value could change between the moments
852-
// when we evaluate it for dst and for src.
853-
//
854-
// Note, this optimization does not apply to OSLICEARR,
855-
// because it does introduce a new pointer into b that was not already there
856-
// (pointer to b itself). After such assignment, if b contents escape,
857-
// b escapes as well. If we ignore such OSLICEARR, we will conclude
858-
// that b does not escape when b contents do.
892+
// Filter out some no-op assignments for escape analysis.
893+
if e.isSliceSelfAssign(n.Left, n.Right) {
859894
if Debug['m'] != 0 {
860895
Warnl(n.Pos, "%v ignoring self-assignment to %S", e.curfnSym(n), n.Left)
861896
}
862897

863898
break
864899
}
865-
866-
// Also skip trivial assignments that assign back to the same object.
867-
//
868-
// It covers these cases:
869-
// val.x = val.y
870-
// val.x[i] = val.y[j]
871-
// val.x1.x2 = val.x1.y2
872-
// ... etc
873-
//
874-
// These assignments do not change assigned object lifetime.
875900
if e.isSelfAssign(n.Left, n.Right) {
876901
if Debug['m'] != 0 {
877902
Warnl(n.Pos, "%v ignoring self-assignment in %S", e.curfnSym(n), n)

Diff for: test/escape2.go

+11-5
Original file line numberDiff line numberDiff line change
@@ -1593,11 +1593,12 @@ func ptrlitEscape() {
15931593
// self-assignments
15941594

15951595
type Buffer struct {
1596-
arr [64]byte
1597-
buf1 []byte
1598-
buf2 []byte
1599-
str1 string
1600-
str2 string
1596+
arr [64]byte
1597+
arrPtr *[64]byte
1598+
buf1 []byte
1599+
buf2 []byte
1600+
str1 string
1601+
str2 string
16011602
}
16021603

16031604
func (b *Buffer) foo() { // ERROR "\(\*Buffer\).foo b does not escape$"
@@ -1611,6 +1612,11 @@ func (b *Buffer) bar() { // ERROR "leaking param: b$"
16111612
b.buf1 = b.arr[1:2] // ERROR "b.arr escapes to heap$"
16121613
}
16131614

1615+
func (b *Buffer) arrayPtr() { // ERROR "\(\*Buffer\).arrayPtr b does not escape"
1616+
b.buf1 = b.arrPtr[1:2] // ERROR "\(\*Buffer\).arrayPtr ignoring self-assignment to b.buf1"
1617+
b.buf1 = b.arrPtr[1:2:3] // ERROR "\(\*Buffer\).arrayPtr ignoring self-assignment to b.buf1"
1618+
}
1619+
16141620
func (b *Buffer) baz() { // ERROR "\(\*Buffer\).baz b does not escape$"
16151621
b.str1 = b.str1[1:2] // ERROR "\(\*Buffer\).baz ignoring self-assignment to b.str1$"
16161622
b.str1 = b.str2[1:2] // ERROR "\(\*Buffer\).baz ignoring self-assignment to b.str1$"

Diff for: test/escape2n.go

+11-5
Original file line numberDiff line numberDiff line change
@@ -1593,11 +1593,12 @@ func ptrlitEscape() {
15931593
// self-assignments
15941594

15951595
type Buffer struct {
1596-
arr [64]byte
1597-
buf1 []byte
1598-
buf2 []byte
1599-
str1 string
1600-
str2 string
1596+
arr [64]byte
1597+
arrPtr *[64]byte
1598+
buf1 []byte
1599+
buf2 []byte
1600+
str1 string
1601+
str2 string
16011602
}
16021603

16031604
func (b *Buffer) foo() { // ERROR "\(\*Buffer\).foo b does not escape$"
@@ -1611,6 +1612,11 @@ func (b *Buffer) bar() { // ERROR "leaking param: b$"
16111612
b.buf1 = b.arr[1:2] // ERROR "b.arr escapes to heap$"
16121613
}
16131614

1615+
func (b *Buffer) arrayPtr() { // ERROR "\(\*Buffer\).arrayPtr b does not escape"
1616+
b.buf1 = b.arrPtr[1:2] // ERROR "\(\*Buffer\).arrayPtr ignoring self-assignment to b.buf1"
1617+
b.buf1 = b.arrPtr[1:2:3] // ERROR "\(\*Buffer\).arrayPtr ignoring self-assignment to b.buf1"
1618+
}
1619+
16141620
func (b *Buffer) baz() { // ERROR "\(\*Buffer\).baz b does not escape$"
16151621
b.str1 = b.str1[1:2] // ERROR "\(\*Buffer\).baz ignoring self-assignment to b.str1$"
16161622
b.str1 = b.str2[1:2] // ERROR "\(\*Buffer\).baz ignoring self-assignment to b.str1$"

0 commit comments

Comments
 (0)