aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/cmd/compile/internal/escape/escape.go8
-rw-r--r--src/cmd/compile/internal/ir/expr.go3
-rw-r--r--src/cmd/compile/internal/noder/stencil.go15
-rw-r--r--src/cmd/compile/internal/test/issue50182_test.go62
4 files changed, 83 insertions, 5 deletions
diff --git a/src/cmd/compile/internal/escape/escape.go b/src/cmd/compile/internal/escape/escape.go
index 61e0121a40..c2145bdf91 100644
--- a/src/cmd/compile/internal/escape/escape.go
+++ b/src/cmd/compile/internal/escape/escape.go
@@ -293,6 +293,14 @@ func (b *batch) finish(fns []*ir.Func) {
// TODO(mdempsky): Update tests to expect this.
goDeferWrapper := n.Op() == ir.OCLOSURE && n.(*ir.ClosureExpr).Func.Wrapper()
+ if n.Op() == ir.OCONVIDATA && n.(*ir.ConvExpr).NonEscaping {
+ // The allocation for the data word of an interface is known to not escape.
+ // See issue 50182.
+ // (But we do still need to process that allocation, as pointers inside
+ // the data word may escape.)
+ loc.escapes = false
+ }
+
if loc.escapes {
if n.Op() == ir.ONAME {
if base.Flag.CompilingRuntime {
diff --git a/src/cmd/compile/internal/ir/expr.go b/src/cmd/compile/internal/ir/expr.go
index f526d987a7..68303c0581 100644
--- a/src/cmd/compile/internal/ir/expr.go
+++ b/src/cmd/compile/internal/ir/expr.go
@@ -250,7 +250,8 @@ func (n *ConstExpr) Val() constant.Value { return n.val }
// It may end up being a value or a type.
type ConvExpr struct {
miniExpr
- X Node
+ X Node
+ NonEscaping bool // The allocation needed for the conversion to interface is known not to escape
}
func NewConvExpr(pos src.XPos, op Op, typ *types.Type, x Node) *ConvExpr {
diff --git a/src/cmd/compile/internal/noder/stencil.go b/src/cmd/compile/internal/noder/stencil.go
index 4c6eaf3fb0..e5f59d0286 100644
--- a/src/cmd/compile/internal/noder/stencil.go
+++ b/src/cmd/compile/internal/noder/stencil.go
@@ -1228,7 +1228,11 @@ func (g *genInst) dictPass(info *instInfo) {
// we do a type assert to the type bound.
mse.X = assertToBound(info, info.dictParam, m.Pos(), mse.X, dst)
} else {
- mse.X = convertUsingDictionary(info, info.dictParam, m.Pos(), mse.X, m, dst)
+ mse.X = convertUsingDictionary(info, info.dictParam, m.Pos(), mse.X, m, dst, true)
+ // Note: we set nonEscaping==true, because we can assume the backing store for the
+ // interface conversion doesn't escape. The method call will immediately go to
+ // a wrapper function which copies all the data out of the interface value.
+ // (It only matters for non-pointer-shaped interface conversions. See issue 50182.)
}
transformDot(mse, false)
}
@@ -1254,7 +1258,7 @@ func (g *genInst) dictPass(info *instInfo) {
// Note: x's argument is still typed as a type parameter.
// m's argument now has an instantiated type.
if mce.X.Type().HasShape() || (mce.X.Type().IsInterface() && m.Type().HasShape()) {
- m = convertUsingDictionary(info, info.dictParam, m.Pos(), m.(*ir.ConvExpr).X, m, m.Type())
+ m = convertUsingDictionary(info, info.dictParam, m.Pos(), m.(*ir.ConvExpr).X, m, m.Type(), false)
}
case ir.ODOTTYPE, ir.ODOTTYPE2:
if !m.Type().HasShape() {
@@ -1347,7 +1351,9 @@ func findDictType(info *instInfo, t *types.Type) int {
// type dst, by returning a new set of nodes that make use of a dictionary entry. in is the
// instantiated node of the CONVIFACE node or XDOT node (for a bound method call) that is causing the
// conversion.
-func convertUsingDictionary(info *instInfo, dictParam *ir.Name, pos src.XPos, v ir.Node, in ir.Node, dst *types.Type) ir.Node {
+// If nonEscaping is true, the caller guarantees that the backing store needed for the interface data
+// word will not escape.
+func convertUsingDictionary(info *instInfo, dictParam *ir.Name, pos src.XPos, v ir.Node, in ir.Node, dst *types.Type, nonEscaping bool) ir.Node {
assert(v.Type().HasShape() || v.Type().IsInterface() && in.Type().HasShape())
assert(dst.IsInterface())
@@ -1417,6 +1423,7 @@ func convertUsingDictionary(info *instInfo, dictParam *ir.Name, pos src.XPos, v
// Figure out what the data field of the interface will be.
data := ir.NewConvExpr(pos, ir.OCONVIDATA, nil, v)
typed(types.Types[types.TUNSAFEPTR], data)
+ data.NonEscaping = nonEscaping
// Build an interface from the type and data parts.
var i ir.Node = ir.NewBinaryExpr(pos, ir.OEFACE, rt, data)
@@ -2147,7 +2154,7 @@ func (g *genInst) buildClosure2(info *instInfo, m ir.Node) ir.Node {
// the type bound.
rcvr = assertToBound(info, dictVar, pos, rcvr, dst)
} else {
- rcvr = convertUsingDictionary(info, dictVar, pos, rcvr, m, dst)
+ rcvr = convertUsingDictionary(info, dictVar, pos, rcvr, m, dst, false)
}
dot := ir.NewSelectorExpr(pos, ir.ODOTINTER, rcvr, m.(*ir.SelectorExpr).Sel)
dot.Selection = typecheck.Lookdot1(dot, dot.Sel, dot.X.Type(), dot.X.Type().AllMethods(), 1)
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))
+ }
+ }
+}