Skip to content

Commit 28f4ea1

Browse files
committed
cmd/compile: improve interface type switches
For type switches where the targets are interface types, call into the runtime once instead of doing a sequence of assert* calls. name old time/op new time/op delta SwitchInterfaceTypePredictable-24 26.6ns ± 1% 25.8ns ± 2% -2.86% (p=0.000 n=10+10) SwitchInterfaceTypeUnpredictable-24 39.3ns ± 1% 37.5ns ± 2% -4.57% (p=0.000 n=10+10) Not super helpful by itself, but this code organization allows followon CLs that add caching to the lookups. Change-Id: I7967f85a99171faa6c2550690e311bea8b54b01c Reviewed-on: https://go-review.googlesource.com/c/go/+/526657 Reviewed-by: Matthew Dempsky <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Cuong Manh Le <[email protected]> Reviewed-by: Keith Randall <[email protected]>
1 parent 10da3b6 commit 28f4ea1

File tree

14 files changed

+742
-355
lines changed

14 files changed

+742
-355
lines changed

src/cmd/compile/internal/ir/node.go

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -282,17 +282,18 @@ const (
282282
// for the captured variables, parameters, retvars, & INLMARK op),
283283
// Body (body of the inlined function), and ReturnVars (list of
284284
// return values)
285-
OINLCALL // intermediary representation of an inlined call.
286-
OMAKEFACE // construct an interface value from rtype/itab and data pointers
287-
OITAB // rtype/itab pointer of an interface value
288-
OIDATA // data pointer of an interface value
289-
OSPTR // base pointer of a slice or string. Bounded==1 means known non-nil.
290-
OCFUNC // reference to c function pointer (not go func value)
291-
OCHECKNIL // emit code to ensure pointer/interface not nil
292-
ORESULT // result of a function call; Xoffset is stack offset
293-
OINLMARK // start of an inlined body, with file/line of caller. Xoffset is an index into the inline tree.
294-
OLINKSYMOFFSET // offset within a name
295-
OJUMPTABLE // A jump table structure for implementing dense expression switches
285+
OINLCALL // intermediary representation of an inlined call.
286+
OMAKEFACE // construct an interface value from rtype/itab and data pointers
287+
OITAB // rtype/itab pointer of an interface value
288+
OIDATA // data pointer of an interface value
289+
OSPTR // base pointer of a slice or string. Bounded==1 means known non-nil.
290+
OCFUNC // reference to c function pointer (not go func value)
291+
OCHECKNIL // emit code to ensure pointer/interface not nil
292+
ORESULT // result of a function call; Xoffset is stack offset
293+
OINLMARK // start of an inlined body, with file/line of caller. Xoffset is an index into the inline tree.
294+
OLINKSYMOFFSET // offset within a name
295+
OJUMPTABLE // A jump table structure for implementing dense expression switches
296+
OINTERFACESWITCH // A type switch with interface cases
296297

297298
// opcodes for generics
298299
ODYNAMICDOTTYPE // x = i.(T) where T is a type parameter (or derived from a type parameter)

src/cmd/compile/internal/ir/node_gen.go

Lines changed: 46 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/cmd/compile/internal/ir/op_string.go

Lines changed: 11 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/cmd/compile/internal/ir/stmt.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ package ir
77
import (
88
"cmd/compile/internal/base"
99
"cmd/compile/internal/types"
10+
"cmd/internal/obj"
1011
"cmd/internal/src"
1112
"go/constant"
1213
)
@@ -309,6 +310,42 @@ func NewJumpTableStmt(pos src.XPos, idx Node) *JumpTableStmt {
309310
return n
310311
}
311312

313+
// An InterfaceSwitchStmt is used to implement type switches.
314+
// Its semantics are:
315+
//
316+
// if RuntimeType implements Descriptor.Cases[0] {
317+
// Case, Itab = 0, itab<RuntimeType, Descriptor.Cases[0]>
318+
// } else if RuntimeType implements Descriptor.Cases[1] {
319+
// Case, Itab = 1, itab<RuntimeType, Descriptor.Cases[1]>
320+
// ...
321+
// } else if RuntimeType implements Descriptor.Cases[N-1] {
322+
// Case, Itab = N-1, itab<RuntimeType, Descriptor.Cases[N-1]>
323+
// } else {
324+
// Case, Itab = len(cases), nil
325+
// }
326+
// RuntimeType must be a non-nil *runtime._type.
327+
// Descriptor must represent an abi.InterfaceSwitch global variable.
328+
type InterfaceSwitchStmt struct {
329+
miniStmt
330+
331+
Case Node
332+
Itab Node
333+
RuntimeType Node
334+
Descriptor *obj.LSym
335+
}
336+
337+
func NewInterfaceSwitchStmt(pos src.XPos, case_, itab, runtimeType Node, descriptor *obj.LSym) *InterfaceSwitchStmt {
338+
n := &InterfaceSwitchStmt{
339+
Case: case_,
340+
Itab: itab,
341+
RuntimeType: runtimeType,
342+
Descriptor: descriptor,
343+
}
344+
n.pos = pos
345+
n.op = OINTERFACESWITCH
346+
return n
347+
}
348+
312349
// An InlineMarkStmt is a marker placed just before an inlined body.
313350
type InlineMarkStmt struct {
314351
miniStmt

src/cmd/compile/internal/ir/symtab.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ type symsStruct struct {
3131
GCWriteBarrier [8]*obj.LSym
3232
Goschedguarded *obj.LSym
3333
Growslice *obj.LSym
34+
InterfaceSwitch *obj.LSym
3435
Memmove *obj.LSym
3536
Msanread *obj.LSym
3637
Msanwrite *obj.LSym

src/cmd/compile/internal/ssagen/ssa.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ func InitConfig() {
118118
ir.Syms.GCWriteBarrier[7] = typecheck.LookupRuntimeFunc("gcWriteBarrier8")
119119
ir.Syms.Goschedguarded = typecheck.LookupRuntimeFunc("goschedguarded")
120120
ir.Syms.Growslice = typecheck.LookupRuntimeFunc("growslice")
121+
ir.Syms.InterfaceSwitch = typecheck.LookupRuntimeFunc("interfaceSwitch")
121122
ir.Syms.Memmove = typecheck.LookupRuntimeFunc("memmove")
122123
ir.Syms.Msanread = typecheck.LookupRuntimeFunc("msanread")
123124
ir.Syms.Msanwrite = typecheck.LookupRuntimeFunc("msanwrite")
@@ -2017,6 +2018,15 @@ func (s *state) stmt(n ir.Node) {
20172018

20182019
s.startBlock(bEnd)
20192020

2021+
case ir.OINTERFACESWITCH:
2022+
n := n.(*ir.InterfaceSwitchStmt)
2023+
2024+
t := s.expr(n.RuntimeType)
2025+
d := s.newValue1A(ssa.OpAddr, s.f.Config.Types.BytePtr, n.Descriptor, s.sb)
2026+
r := s.rtcall(ir.Syms.InterfaceSwitch, true, []*types.Type{s.f.Config.Types.Int, s.f.Config.Types.BytePtr}, d, t)
2027+
s.assign(n.Case, r[0], false, 0)
2028+
s.assign(n.Itab, r[1], false, 0)
2029+
20202030
case ir.OCHECKNIL:
20212031
n := n.(*ir.UnaryExpr)
20222032
p := s.expr(n.X)

src/cmd/compile/internal/test/switch_test.go

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,123 @@ func benchmarkSwitchType(b *testing.B, predictable bool) {
160160
n += 8
161161
}
162162
}
163+
sink = n
164+
}
165+
166+
func BenchmarkSwitchInterfaceTypePredictable(b *testing.B) {
167+
benchmarkSwitchInterfaceType(b, true)
168+
}
169+
func BenchmarkSwitchInterfaceTypeUnpredictable(b *testing.B) {
170+
benchmarkSwitchInterfaceType(b, false)
171+
}
172+
173+
type SI0 interface {
174+
si0()
175+
}
176+
type ST0 struct {
177+
}
178+
179+
func (ST0) si0() {
180+
}
181+
182+
type SI1 interface {
183+
si1()
184+
}
185+
type ST1 struct {
186+
}
187+
188+
func (ST1) si1() {
189+
}
190+
191+
type SI2 interface {
192+
si2()
193+
}
194+
type ST2 struct {
195+
}
196+
197+
func (ST2) si2() {
198+
}
199+
200+
type SI3 interface {
201+
si3()
202+
}
203+
type ST3 struct {
204+
}
205+
206+
func (ST3) si3() {
207+
}
208+
209+
type SI4 interface {
210+
si4()
211+
}
212+
type ST4 struct {
213+
}
214+
215+
func (ST4) si4() {
216+
}
217+
218+
type SI5 interface {
219+
si5()
220+
}
221+
type ST5 struct {
222+
}
223+
224+
func (ST5) si5() {
225+
}
226+
227+
type SI6 interface {
228+
si6()
229+
}
230+
type ST6 struct {
231+
}
232+
233+
func (ST6) si6() {
234+
}
235+
236+
type SI7 interface {
237+
si7()
238+
}
239+
type ST7 struct {
240+
}
241+
242+
func (ST7) si7() {
243+
}
244+
245+
func benchmarkSwitchInterfaceType(b *testing.B, predictable bool) {
246+
a := []any{
247+
ST0{},
248+
ST1{},
249+
ST2{},
250+
ST3{},
251+
ST4{},
252+
ST5{},
253+
ST6{},
254+
ST7{},
255+
}
256+
n := 0
257+
rng := newRNG()
258+
for i := 0; i < b.N; i++ {
259+
rng = rng.next(predictable)
260+
switch a[rng.value()&7].(type) {
261+
case SI0:
262+
n += 1
263+
case SI1:
264+
n += 2
265+
case SI2:
266+
n += 3
267+
case SI3:
268+
n += 4
269+
case SI4:
270+
n += 5
271+
case SI5:
272+
n += 6
273+
case SI6:
274+
n += 7
275+
case SI7:
276+
n += 8
277+
}
278+
}
279+
sink = n
163280
}
164281

165282
// A simple random number generator used to make switches conditionally predictable.

src/cmd/compile/internal/typecheck/_builtin/runtime.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,9 @@ func panicdottypeE(have, want, iface *byte)
112112
func panicdottypeI(have, want, iface *byte)
113113
func panicnildottype(want *byte)
114114

115+
// interface switches
116+
func interfaceSwitch(s *byte, t *byte) (int, *byte)
117+
115118
// interface equality. Type/itab pointers are already known to be equal, so
116119
// we only need to pass one.
117120
func ifaceeq(tab *uintptr, x, y unsafe.Pointer) (ret bool)

0 commit comments

Comments
 (0)