Commit 6129f373 authored by Keith Randall's avatar Keith Randall

cmd/compile: inline convT2{I,E} when result doesn't escape

No point in calling a function when we can build the interface
using a known type (or itab) and the address of a local.

Get rid of third arg (preallocated stack space) to convT2{I,E}.

Makes go binary smaller by 0.2%

benchmark                   old ns/op     new ns/op     delta
BenchmarkEfaceInteger-8     16.7          10.1          -39.52%

Update #17118
Update #15375

Change-Id: I9724a1f802bfa1e3957bf1856b55558278e198a2
Reviewed-on: https://go-review.googlesource.com/29373
Run-TryBot: Keith Randall <khr@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: default avatarMatthew Dempsky <mdempsky@google.com>
parent 892d146a
This diff is collapsed.
...@@ -60,8 +60,8 @@ func slicestringcopy(to any, fr any) int ...@@ -60,8 +60,8 @@ func slicestringcopy(to any, fr any) int
// interface conversions // interface conversions
func convI2E(elem any) (ret any) func convI2E(elem any) (ret any)
func convI2I(typ *byte, elem any) (ret any) func convI2I(typ *byte, elem any) (ret any)
func convT2E(typ *byte, elem, buf *any) (ret any) func convT2E(typ *byte, elem *any) (ret any)
func convT2I(tab *byte, elem, buf *any) (ret any) func convT2I(tab *byte, elem *any) (ret any)
// interface type assertions x.(T) // interface type assertions x.(T)
func assertE2E(typ *byte, iface any, ret *any) func assertE2E(typ *byte, iface any, ret *any)
......
...@@ -18,7 +18,7 @@ func makeT() T { ...@@ -18,7 +18,7 @@ func makeT() T {
var g T var g T
var sink []byte var sink interface{}
func TestIssue15854(t *testing.T) { func TestIssue15854(t *testing.T) {
for i := 0; i < 10000; i++ { for i := 0; i < 10000; i++ {
......
// Copyright 2016 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 gc
// Test to make sure we make copies of the values we
// put in interfaces.
import (
"testing"
)
var x int
func TestEfaceConv1(t *testing.T) {
a := 5
i := interface{}(a)
a += 2
if got := i.(int); got != 5 {
t.Errorf("wanted 5, got %d\n", got)
}
}
func TestEfaceConv2(t *testing.T) {
a := 5
sink = &a
i := interface{}(a)
a += 2
if got := i.(int); got != 5 {
t.Errorf("wanted 5, got %d\n", got)
}
}
func TestEfaceConv3(t *testing.T) {
x = 5
if got := e2int3(x); got != 5 {
t.Errorf("wanted 5, got %d\n", got)
}
}
//go:noinline
func e2int3(i interface{}) int {
x = 7
return i.(int)
}
func TestEfaceConv4(t *testing.T) {
a := 5
if got := e2int4(a, &a); got != 5 {
t.Errorf("wanted 5, got %d\n", got)
}
}
//go:noinline
func e2int4(i interface{}, p *int) int {
*p = 7
return i.(int)
}
type Int int
var y Int
type I interface {
foo()
}
func (i Int) foo() {
}
func TestIfaceConv1(t *testing.T) {
a := Int(5)
i := interface{}(a)
a += 2
if got := i.(Int); got != 5 {
t.Errorf("wanted 5, got %d\n", int(got))
}
}
func TestIfaceConv2(t *testing.T) {
a := Int(5)
sink = &a
i := interface{}(a)
a += 2
if got := i.(Int); got != 5 {
t.Errorf("wanted 5, got %d\n", int(got))
}
}
func TestIfaceConv3(t *testing.T) {
y = 5
if got := i2Int3(y); got != 5 {
t.Errorf("wanted 5, got %d\n", int(got))
}
}
//go:noinline
func i2Int3(i I) Int {
y = 7
return i.(Int)
}
func TestIfaceConv4(t *testing.T) {
a := Int(5)
if got := i2Int4(a, &a); got != 5 {
t.Errorf("wanted 5, got %d\n", int(got))
}
}
//go:noinline
func i2Int4(i I, p *Int) Int {
*p = 7
return i.(Int)
}
func BenchmarkEfaceInteger(b *testing.B) {
sum := 0
for i := 0; i < b.N; i++ {
sum += i2int(i)
}
sink = sum
}
//go:noinline
func i2int(i interface{}) int {
return i.(int)
}
...@@ -1058,6 +1058,25 @@ opswitch: ...@@ -1058,6 +1058,25 @@ opswitch:
n = l n = l
break break
} }
// Optimize convT2{E,I} when T is not pointer-shaped.
// We make the interface by initializing a stack temporary to
// the value we want to put in the interface, then using the address of
// that stack temporary for the interface data word.
if !n.Left.Type.IsInterface() && n.Esc == EscNone && n.Left.Type.Width <= 1024 {
tmp := temp(n.Left.Type)
init.Append(typecheck(nod(OAS, tmp, n.Left), Etop))
var t *Node
if n.Type.IsEmptyInterface() {
t = typename(n.Left.Type)
} else {
t = itabname(n.Left.Type, n.Type)
}
l := nod(OEFACE, t, typecheck(nod(OADDR, tmp, nil), Erv))
l.Type = n.Type
l.Typecheck = n.Typecheck
n = l
break
}
var ll []*Node var ll []*Node
if n.Type.IsEmptyInterface() { if n.Type.IsEmptyInterface() {
...@@ -1087,25 +1106,10 @@ opswitch: ...@@ -1087,25 +1106,10 @@ opswitch:
ll = append(ll, nod(OADDR, copyexpr(n.Left, n.Left.Type, init), nil)) ll = append(ll, nod(OADDR, copyexpr(n.Left, n.Left.Type, init), nil))
} }
dowidth(n.Left.Type) dowidth(n.Left.Type)
r := nodnil()
if n.Esc == EscNone && n.Left.Type.Width <= 1024 {
// Allocate stack buffer for value stored in interface.
r = temp(n.Left.Type)
r = nod(OAS, r, nil) // zero temp
r = typecheck(r, Etop)
init.Append(r)
r = nod(OADDR, r.Left, nil)
r = typecheck(r, Erv)
}
ll = append(ll, r)
} }
fn := syslook(convFuncName(n.Left.Type, n.Type)) fn := syslook(convFuncName(n.Left.Type, n.Type))
if !n.Left.Type.IsInterface() { fn = substArgTypes(fn, n.Left.Type, n.Type)
fn = substArgTypes(fn, n.Left.Type, n.Left.Type, n.Type)
} else {
fn = substArgTypes(fn, n.Left.Type, n.Type)
}
dowidth(fn.Type) dowidth(fn.Type)
n = nod(OCALL, fn, nil) n = nod(OCALL, fn, nil)
n.List.Set(ll) n.List.Set(ll)
......
...@@ -152,7 +152,7 @@ func itabsinit() { ...@@ -152,7 +152,7 @@ func itabsinit() {
unlock(&ifaceLock) unlock(&ifaceLock)
} }
func convT2E(t *_type, elem unsafe.Pointer, x unsafe.Pointer) (e eface) { func convT2E(t *_type, elem unsafe.Pointer) (e eface) {
if raceenabled { if raceenabled {
raceReadObjectPC(t, elem, getcallerpc(unsafe.Pointer(&t)), funcPC(convT2E)) raceReadObjectPC(t, elem, getcallerpc(unsafe.Pointer(&t)), funcPC(convT2E))
} }
...@@ -162,18 +162,16 @@ func convT2E(t *_type, elem unsafe.Pointer, x unsafe.Pointer) (e eface) { ...@@ -162,18 +162,16 @@ func convT2E(t *_type, elem unsafe.Pointer, x unsafe.Pointer) (e eface) {
if isDirectIface(t) { if isDirectIface(t) {
throw("direct convT2E") throw("direct convT2E")
} }
if x == nil { x := newobject(t)
x = newobject(t) // TODO: We allocate a zeroed object only to overwrite it with
// TODO: We allocate a zeroed object only to overwrite it with // actual data. Figure out how to avoid zeroing. Also below in convT2I.
// actual data. Figure out how to avoid zeroing. Also below in convT2I.
}
typedmemmove(t, x, elem) typedmemmove(t, x, elem)
e._type = t e._type = t
e.data = x e.data = x
return return
} }
func convT2I(tab *itab, elem unsafe.Pointer, x unsafe.Pointer) (i iface) { func convT2I(tab *itab, elem unsafe.Pointer) (i iface) {
t := tab._type t := tab._type
if raceenabled { if raceenabled {
raceReadObjectPC(t, elem, getcallerpc(unsafe.Pointer(&tab)), funcPC(convT2I)) raceReadObjectPC(t, elem, getcallerpc(unsafe.Pointer(&tab)), funcPC(convT2I))
...@@ -184,9 +182,7 @@ func convT2I(tab *itab, elem unsafe.Pointer, x unsafe.Pointer) (i iface) { ...@@ -184,9 +182,7 @@ func convT2I(tab *itab, elem unsafe.Pointer, x unsafe.Pointer) (i iface) {
if isDirectIface(t) { if isDirectIface(t) {
throw("direct convT2I") throw("direct convT2I")
} }
if x == nil { x := newobject(t)
x = newobject(t)
}
typedmemmove(t, x, elem) typedmemmove(t, x, elem)
i.tab = tab i.tab = tab
i.data = x i.data = x
......
...@@ -139,7 +139,9 @@ var i9 interface{} ...@@ -139,7 +139,9 @@ var i9 interface{}
func f9() bool { func f9() bool {
g8() g8()
x := i9 x := i9
return x != interface{}(99.0i) // ERROR "live at call to convT2E: x.data x.type$" y := interface{}(99.0i) // ERROR "live at call to convT2E: x.data x.type$"
i9 = y // make y escape so the line above has to call convT2E
return x != y
} }
// liveness formerly confused by UNDEF followed by RET, // liveness formerly confused by UNDEF followed by RET,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment