Skip to content

Commit 2270133

Browse files
committed
cmd/gc: allocate backing storage for non-escaping interfaces on stack
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>
1 parent cf7461c commit 2270133

7 files changed

Lines changed: 72 additions & 13 deletions

File tree

src/cmd/internal/gc/builtin.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,8 @@ const runtimeimport = "" +
5050
"func @\"\".typ2Itab (@\"\".typ·2 *byte, @\"\".typ2·3 *byte, @\"\".cache·4 **byte) (@\"\".ret·1 *byte)\n" +
5151
"func @\"\".convI2E (@\"\".elem·2 any) (@\"\".ret·1 any)\n" +
5252
"func @\"\".convI2I (@\"\".typ·2 *byte, @\"\".elem·3 any) (@\"\".ret·1 any)\n" +
53-
"func @\"\".convT2E (@\"\".typ·2 *byte, @\"\".elem·3 *any) (@\"\".ret·1 any)\n" +
54-
"func @\"\".convT2I (@\"\".typ·2 *byte, @\"\".typ2·3 *byte, @\"\".cache·4 **byte, @\"\".elem·5 *any) (@\"\".ret·1 any)\n" +
53+
"func @\"\".convT2E (@\"\".typ·2 *byte, @\"\".elem·3 *any, @\"\".buf·4 *any) (@\"\".ret·1 any)\n" +
54+
"func @\"\".convT2I (@\"\".typ·2 *byte, @\"\".typ2·3 *byte, @\"\".cache·4 **byte, @\"\".elem·5 *any, @\"\".buf·6 *any) (@\"\".ret·1 any)\n" +
5555
"func @\"\".assertE2E (@\"\".typ·1 *byte, @\"\".iface·2 any, @\"\".ret·3 *any)\n" +
5656
"func @\"\".assertE2E2 (@\"\".typ·2 *byte, @\"\".iface·3 any, @\"\".ret·4 *any) (? bool)\n" +
5757
"func @\"\".assertE2I (@\"\".typ·1 *byte, @\"\".iface·2 any, @\"\".ret·3 *any)\n" +

src/cmd/internal/gc/builtin/runtime.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,8 @@ func slicestringcopy(to any, fr any) int
6363
func typ2Itab(typ *byte, typ2 *byte, cache **byte) (ret *byte)
6464
func convI2E(elem any) (ret any)
6565
func convI2I(typ *byte, elem any) (ret any)
66-
func convT2E(typ *byte, elem *any) (ret any)
67-
func convT2I(typ *byte, typ2 *byte, cache **byte, elem *any) (ret any)
66+
func convT2E(typ *byte, elem, buf *any) (ret any)
67+
func convT2I(typ *byte, typ2 *byte, cache **byte, elem, buf *any) (ret any)
6868

6969
// interface type assertions x.(T)
7070
func assertE2E(typ *byte, iface any, ret *any)

src/cmd/internal/gc/esc.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -653,12 +653,11 @@ func esc(e *EscState, n *Node, up *Node) {
653653
}
654654
}
655655

656-
case OCONV, OCONVNOP:
656+
case OCONV,
657+
OCONVNOP:
657658
escassign(e, n, n.Left)
658659

659660
case OCONVIFACE:
660-
// We don't allocate storage for OCONVIFACE on stack yet,
661-
// but mark it as EscNone merely to get debug output for tests.
662661
n.Esc = EscNone // until proven otherwise
663662
e.noesc = list(e.noesc, n)
664663
n.Escloopdepth = e.loopdepth

src/cmd/internal/gc/walk.go

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1040,9 +1040,25 @@ func walkexpr(np **Node, init **NodeList) {
10401040
} else {
10411041
ll = list(ll, Nod(OADDR, copyexpr(n.Left, n.Left.Type, init), nil))
10421042
}
1043+
dowidth(n.Left.Type)
1044+
r := nodnil()
1045+
if n.Esc == EscNone && n.Left.Type.Width <= 1024 {
1046+
// Allocate stack buffer for value stored in interface.
1047+
r = temp(n.Left.Type)
1048+
r = Nod(OAS, r, nil) // zero temp
1049+
typecheck(&r, Etop)
1050+
*init = list(*init, r)
1051+
r = Nod(OADDR, r.Left, nil)
1052+
typecheck(&r, Erv)
1053+
}
1054+
ll = list(ll, r)
10431055
}
10441056

1045-
substArgTypes(fn, n.Left.Type, n.Type)
1057+
if !Isinter(n.Left.Type) {
1058+
substArgTypes(fn, n.Left.Type, n.Left.Type, n.Type)
1059+
} else {
1060+
substArgTypes(fn, n.Left.Type, n.Type)
1061+
}
10461062
dowidth(fn.Type)
10471063
n = Nod(OCALL, fn, nil)
10481064
n.List = ll

src/runtime/iface.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -130,13 +130,15 @@ func typ2Itab(t *_type, inter *interfacetype, cache **itab) *itab {
130130
return tab
131131
}
132132

133-
func convT2E(t *_type, elem unsafe.Pointer) (e interface{}) {
133+
func convT2E(t *_type, elem unsafe.Pointer, x unsafe.Pointer) (e interface{}) {
134134
ep := (*eface)(unsafe.Pointer(&e))
135135
if isDirectIface(t) {
136136
ep._type = t
137137
typedmemmove(t, unsafe.Pointer(&ep.data), elem)
138138
} else {
139-
x := newobject(t)
139+
if x == nil {
140+
x = newobject(t)
141+
}
140142
// TODO: We allocate a zeroed object only to overwrite it with
141143
// actual data. Figure out how to avoid zeroing. Also below in convT2I.
142144
typedmemmove(t, x, elem)
@@ -146,7 +148,7 @@ func convT2E(t *_type, elem unsafe.Pointer) (e interface{}) {
146148
return
147149
}
148150

149-
func convT2I(t *_type, inter *interfacetype, cache **itab, elem unsafe.Pointer) (i fInterface) {
151+
func convT2I(t *_type, inter *interfacetype, cache **itab, elem unsafe.Pointer, x unsafe.Pointer) (i fInterface) {
150152
tab := (*itab)(atomicloadp(unsafe.Pointer(cache)))
151153
if tab == nil {
152154
tab = getitab(inter, t, false)
@@ -157,7 +159,9 @@ func convT2I(t *_type, inter *interfacetype, cache **itab, elem unsafe.Pointer)
157159
pi.tab = tab
158160
typedmemmove(t, unsafe.Pointer(&pi.data), elem)
159161
} else {
160-
x := newobject(t)
162+
if x == nil {
163+
x = newobject(t)
164+
}
161165
typedmemmove(t, x, elem)
162166
pi.tab = tab
163167
pi.data = x

src/runtime/iface_test.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,3 +221,43 @@ func BenchmarkAssertE2E2Blank(b *testing.B) {
221221
_, ok = e.(interface{})
222222
}
223223
}
224+
225+
func TestNonEscapingConvT2E(t *testing.T) {
226+
m := make(map[interface{}]bool)
227+
m[42] = true
228+
if !m[42] {
229+
t.Fatalf("42 is not present in the map")
230+
}
231+
if m[0] {
232+
t.Fatalf("0 is present in the map")
233+
}
234+
235+
n := testing.AllocsPerRun(1000, func() {
236+
if m[0] {
237+
t.Fatalf("0 is present in the map")
238+
}
239+
})
240+
if n != 0 {
241+
t.Fatalf("want 0 allocs, got %v", n)
242+
}
243+
}
244+
245+
func TestNonEscapingConvT2I(t *testing.T) {
246+
m := make(map[I1]bool)
247+
m[TM(42)] = true
248+
if !m[TM(42)] {
249+
t.Fatalf("42 is not present in the map")
250+
}
251+
if m[TM(0)] {
252+
t.Fatalf("0 is present in the map")
253+
}
254+
255+
n := testing.AllocsPerRun(1000, func() {
256+
if m[TM(0)] {
257+
t.Fatalf("0 is present in the map")
258+
}
259+
})
260+
if n != 0 {
261+
t.Fatalf("want 0 allocs, got %v", n)
262+
}
263+
}

test/escape2.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1787,7 +1787,7 @@ func makemap1() map[int]int {
17871787

17881788
func makemap2() {
17891789
m := make(map[int]int) // ERROR "make\(map\[int\]int\) escapes to heap"
1790-
sink = m // ERROR "m escapes to heap"
1790+
sink = m // ERROR "m escapes to heap"
17911791
}
17921792

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

0 commit comments

Comments
 (0)