diff options
-rw-r--r-- | src/cmd/compile/internal/escape/escape.go | 8 | ||||
-rw-r--r-- | src/cmd/compile/internal/ir/expr.go | 3 | ||||
-rw-r--r-- | src/cmd/compile/internal/noder/stencil.go | 15 | ||||
-rw-r--r-- | src/cmd/compile/internal/test/issue50182_test.go | 62 |
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)) + } + } +} |