Skip to content

Commit

Permalink
cmd/gc: allocate backing storage for non-escaping interfaces on stack
Browse files Browse the repository at this point in the history
Extend escape analysis to convT2E and conT2I. If the interface value
does not escape supply runtime with a stack buffer for the object copy.

This is a straight port from .c to .go of Dmitry's patch

Change-Id: Ic315dd50d144d94dd3324227099c116be5ca70b6
Reviewed-on: https://go-review.googlesource.com/8201
Reviewed-by: Dmitry Vyukov <dvyukov@google.com>
  • Loading branch information
dr2chase committed Mar 30, 2015
1 parent cf7461c commit 2270133
Show file tree
Hide file tree
Showing 7 changed files with 72 additions and 13 deletions.
4 changes: 2 additions & 2 deletions src/cmd/internal/gc/builtin.go
Expand Up @@ -50,8 +50,8 @@ const runtimeimport = "" +
"func @\"\".typ2Itab (@\"\".typ·2 *byte, @\"\".typ2·3 *byte, @\"\".cache·4 **byte) (@\"\".ret·1 *byte)\n" + "func @\"\".typ2Itab (@\"\".typ·2 *byte, @\"\".typ2·3 *byte, @\"\".cache·4 **byte) (@\"\".ret·1 *byte)\n" +
"func @\"\".convI2E (@\"\".elem·2 any) (@\"\".ret·1 any)\n" + "func @\"\".convI2E (@\"\".elem·2 any) (@\"\".ret·1 any)\n" +
"func @\"\".convI2I (@\"\".typ·2 *byte, @\"\".elem·3 any) (@\"\".ret·1 any)\n" + "func @\"\".convI2I (@\"\".typ·2 *byte, @\"\".elem·3 any) (@\"\".ret·1 any)\n" +
"func @\"\".convT2E (@\"\".typ·2 *byte, @\"\".elem·3 *any) (@\"\".ret·1 any)\n" + "func @\"\".convT2E (@\"\".typ·2 *byte, @\"\".elem·3 *any, @\"\".buf·4 *any) (@\"\".ret·1 any)\n" +
"func @\"\".convT2I (@\"\".typ·2 *byte, @\"\".typ2·3 *byte, @\"\".cache·4 **byte, @\"\".elem·5 *any) (@\"\".ret·1 any)\n" + "func @\"\".convT2I (@\"\".typ·2 *byte, @\"\".typ2·3 *byte, @\"\".cache·4 **byte, @\"\".elem·5 *any, @\"\".buf·6 *any) (@\"\".ret·1 any)\n" +
"func @\"\".assertE2E (@\"\".typ·1 *byte, @\"\".iface·2 any, @\"\".ret·3 *any)\n" + "func @\"\".assertE2E (@\"\".typ·1 *byte, @\"\".iface·2 any, @\"\".ret·3 *any)\n" +
"func @\"\".assertE2E2 (@\"\".typ·2 *byte, @\"\".iface·3 any, @\"\".ret·4 *any) (? bool)\n" + "func @\"\".assertE2E2 (@\"\".typ·2 *byte, @\"\".iface·3 any, @\"\".ret·4 *any) (? bool)\n" +
"func @\"\".assertE2I (@\"\".typ·1 *byte, @\"\".iface·2 any, @\"\".ret·3 *any)\n" + "func @\"\".assertE2I (@\"\".typ·1 *byte, @\"\".iface·2 any, @\"\".ret·3 *any)\n" +
Expand Down
4 changes: 2 additions & 2 deletions src/cmd/internal/gc/builtin/runtime.go
Expand Up @@ -63,8 +63,8 @@ func slicestringcopy(to any, fr any) int
func typ2Itab(typ *byte, typ2 *byte, cache **byte) (ret *byte) func typ2Itab(typ *byte, typ2 *byte, cache **byte) (ret *byte)
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 *any) (ret any) func convT2E(typ *byte, elem, buf *any) (ret any)
func convT2I(typ *byte, typ2 *byte, cache **byte, elem *any) (ret any) func convT2I(typ *byte, typ2 *byte, cache **byte, elem, buf *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)
Expand Down
5 changes: 2 additions & 3 deletions src/cmd/internal/gc/esc.go
Expand Up @@ -653,12 +653,11 @@ func esc(e *EscState, n *Node, up *Node) {
} }
} }


case OCONV, OCONVNOP: case OCONV,
OCONVNOP:
escassign(e, n, n.Left) escassign(e, n, n.Left)


case OCONVIFACE: case OCONVIFACE:
// We don't allocate storage for OCONVIFACE on stack yet,
// but mark it as EscNone merely to get debug output for tests.
n.Esc = EscNone // until proven otherwise n.Esc = EscNone // until proven otherwise
e.noesc = list(e.noesc, n) e.noesc = list(e.noesc, n)
n.Escloopdepth = e.loopdepth n.Escloopdepth = e.loopdepth
Expand Down
18 changes: 17 additions & 1 deletion src/cmd/internal/gc/walk.go
Expand Up @@ -1040,9 +1040,25 @@ func walkexpr(np **Node, init **NodeList) {
} else { } else {
ll = list(ll, Nod(OADDR, copyexpr(n.Left, n.Left.Type, init), nil)) ll = list(ll, Nod(OADDR, copyexpr(n.Left, n.Left.Type, init), nil))
} }
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
typecheck(&r, Etop)
*init = list(*init, r)
r = Nod(OADDR, r.Left, nil)
typecheck(&r, Erv)
}
ll = list(ll, r)
} }


substArgTypes(fn, n.Left.Type, n.Type) if !Isinter(n.Left.Type) {
substArgTypes(fn, n.Left.Type, n.Left.Type, n.Type)
} else {
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 = ll n.List = ll
Expand Down
12 changes: 8 additions & 4 deletions src/runtime/iface.go
Expand Up @@ -130,13 +130,15 @@ func typ2Itab(t *_type, inter *interfacetype, cache **itab) *itab {
return tab return tab
} }


func convT2E(t *_type, elem unsafe.Pointer) (e interface{}) { func convT2E(t *_type, elem unsafe.Pointer, x unsafe.Pointer) (e interface{}) {
ep := (*eface)(unsafe.Pointer(&e)) ep := (*eface)(unsafe.Pointer(&e))
if isDirectIface(t) { if isDirectIface(t) {
ep._type = t ep._type = t
typedmemmove(t, unsafe.Pointer(&ep.data), elem) typedmemmove(t, unsafe.Pointer(&ep.data), elem)
} else { } else {
x := newobject(t) if x == nil {
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)
Expand All @@ -146,7 +148,7 @@ func convT2E(t *_type, elem unsafe.Pointer) (e interface{}) {
return return
} }


func convT2I(t *_type, inter *interfacetype, cache **itab, elem unsafe.Pointer) (i fInterface) { func convT2I(t *_type, inter *interfacetype, cache **itab, elem unsafe.Pointer, x unsafe.Pointer) (i fInterface) {
tab := (*itab)(atomicloadp(unsafe.Pointer(cache))) tab := (*itab)(atomicloadp(unsafe.Pointer(cache)))
if tab == nil { if tab == nil {
tab = getitab(inter, t, false) tab = getitab(inter, t, false)
Expand All @@ -157,7 +159,9 @@ func convT2I(t *_type, inter *interfacetype, cache **itab, elem unsafe.Pointer)
pi.tab = tab pi.tab = tab
typedmemmove(t, unsafe.Pointer(&pi.data), elem) typedmemmove(t, unsafe.Pointer(&pi.data), elem)
} else { } else {
x := newobject(t) if x == nil {
x = newobject(t)
}
typedmemmove(t, x, elem) typedmemmove(t, x, elem)
pi.tab = tab pi.tab = tab
pi.data = x pi.data = x
Expand Down
40 changes: 40 additions & 0 deletions src/runtime/iface_test.go
Expand Up @@ -221,3 +221,43 @@ func BenchmarkAssertE2E2Blank(b *testing.B) {
_, ok = e.(interface{}) _, ok = e.(interface{})
} }
} }

func TestNonEscapingConvT2E(t *testing.T) {
m := make(map[interface{}]bool)
m[42] = true
if !m[42] {
t.Fatalf("42 is not present in the map")
}
if m[0] {
t.Fatalf("0 is present in the map")
}

n := testing.AllocsPerRun(1000, func() {
if m[0] {
t.Fatalf("0 is present in the map")
}
})
if n != 0 {
t.Fatalf("want 0 allocs, got %v", n)
}
}

func TestNonEscapingConvT2I(t *testing.T) {
m := make(map[I1]bool)
m[TM(42)] = true
if !m[TM(42)] {
t.Fatalf("42 is not present in the map")
}
if m[TM(0)] {
t.Fatalf("0 is present in the map")
}

n := testing.AllocsPerRun(1000, func() {
if m[TM(0)] {
t.Fatalf("0 is present in the map")
}
})
if n != 0 {
t.Fatalf("want 0 allocs, got %v", n)
}
}
2 changes: 1 addition & 1 deletion test/escape2.go
Expand Up @@ -1787,7 +1787,7 @@ func makemap1() map[int]int {


func makemap2() { func makemap2() {
m := make(map[int]int) // ERROR "make\(map\[int\]int\) escapes to heap" m := make(map[int]int) // ERROR "make\(map\[int\]int\) escapes to heap"
sink = m // ERROR "m escapes to heap" sink = m // ERROR "m escapes to heap"
} }


func nonescapingEface(m map[interface{}]bool) bool { // ERROR "m does not escape" func nonescapingEface(m map[interface{}]bool) bool { // ERROR "m does not escape"
Expand Down

0 comments on commit 2270133

Please sign in to comment.