aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/compile/internal/test/issue50182_test.go
diff options
context:
space:
mode:
authorKeith Randall <khr@golang.org>2022-01-12 16:11:35 -0800
committerKeith Randall <khr@golang.org>2022-01-13 23:35:37 +0000
commite550c3054586a224d949cc8fa030bac0887bee51 (patch)
tree58c801f8818ce242c7f6094302361d333c99a855 /src/cmd/compile/internal/test/issue50182_test.go
parent4f6f68ee4b50162d3bd01efb9b8a5f6a10dc54af (diff)
downloadgo-e550c3054586a224d949cc8fa030bac0887bee51.tar.gz
go-e550c3054586a224d949cc8fa030bac0887bee51.zip
cmd/compile: stop interface conversions for generic method calls from allocating
Let T be a type parameter, and say we instantiate it with S, a type that isn't pointer-like (e.g. a pair of ints, or as in 50182, a slice). Then to call a method m on a variable of type T, the compiler does essentially: var v T = ... i := (interface{m()})(v) i.m() The conversion at that second line allocates, as we need to make the data word for an interface. And in the general case, that interface may live an arbitrarily long time. But in this case, we know it doesn't. The data word of i has type *S. When we call i.m, we can't call S.m directly. It is expecting an S, not a *S. We call through a wrapper defined on *S, which looks like: func (p *S) m() { var s S = *p s.m() } The value passed in for p is exactly the data word mentioned above. It never escapes anywhere - the wrapper copies a type S variable out of *p and p is dead after that. That means that in the situation where we build an interface for the explicit purpose of calling a method on it, and use that built interface nowhere else, the allocation of the data word for that interface is known to die before the call returns and thus can be stack allocated. One tricky case is that although the allocation of the backing store of the interface conversion doesn't escape, pointers we store *inside* that allocation might escape (in fact they definitely will, unless we can devirtualize the receiver). Fixes #50182 Change-Id: I40e893955c2e6871c54ccecf1b9f0cae17871b0d Reviewed-on: https://go-review.googlesource.com/c/go/+/378178 Trust: Keith Randall <khr@golang.org> Run-TryBot: Keith Randall <khr@golang.org> Trust: Dan Scales <danscales@google.com> Reviewed-by: Matthew Dempsky <mdempsky@google.com> Reviewed-by: David Chase <drchase@google.com> Reviewed-by: Dan Scales <danscales@google.com> TryBot-Result: Gopher Robot <gobot@golang.org>
Diffstat (limited to 'src/cmd/compile/internal/test/issue50182_test.go')
-rw-r--r--src/cmd/compile/internal/test/issue50182_test.go62
1 files changed, 62 insertions, 0 deletions
diff --git a/src/cmd/compile/internal/test/issue50182_test.go b/src/cmd/compile/internal/test/issue50182_test.go
new file mode 100644
index 0000000000..cd277fa285
--- /dev/null
+++ b/src/cmd/compile/internal/test/issue50182_test.go
@@ -0,0 +1,62 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package test
+
+import (
+ "fmt"
+ "sort"
+ "testing"
+)
+
+// Test that calling methods on generic types doesn't cause allocations.
+func genericSorted[T sort.Interface](data T) bool {
+ n := data.Len()
+ for i := n - 1; i > 0; i-- {
+ if data.Less(i, i-1) {
+ return false
+ }
+ }
+ return true
+}
+func TestGenericSorted(t *testing.T) {
+ var data = sort.IntSlice{-10, -5, 0, 1, 2, 3, 5, 7, 11, 100, 100, 100, 1000, 10000}
+ f := func() {
+ genericSorted(data)
+ }
+ if n := testing.AllocsPerRun(10, f); n > 0 {
+ t.Errorf("got %f allocs, want 0", n)
+ }
+}
+
+// Test that escape analysis correctly tracks escaping inside of methods
+// called on generic types.
+type fooer interface {
+ foo()
+}
+type P struct {
+ p *int
+ q int
+}
+
+var esc []*int
+
+func (p P) foo() {
+ esc = append(esc, p.p) // foo escapes the pointer from inside of p
+}
+func f[T fooer](t T) {
+ t.foo()
+}
+func TestGenericEscape(t *testing.T) {
+ for i := 0; i < 4; i++ {
+ var x int = 77 + i
+ var p P = P{p: &x}
+ f(p)
+ }
+ for i, p := range esc {
+ if got, want := *p, 77+i; got != want {
+ panic(fmt.Sprintf("entry %d: got %d, want %d", i, got, want))
+ }
+ }
+}