Skip to content

Commit

Permalink
cmd/compile: improve interface type switches
Browse files Browse the repository at this point in the history
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 <mdempsky@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Cuong Manh Le <cuong.manhle.vn@gmail.com>
Reviewed-by: Keith Randall <khr@google.com>
  • Loading branch information
randall77 committed Oct 6, 2023
1 parent 10da3b6 commit 28f4ea1
Show file tree
Hide file tree
Showing 14 changed files with 742 additions and 355 deletions.
23 changes: 12 additions & 11 deletions src/cmd/compile/internal/ir/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -282,17 +282,18 @@ const (
// for the captured variables, parameters, retvars, & INLMARK op),
// Body (body of the inlined function), and ReturnVars (list of
// return values)
OINLCALL // intermediary representation of an inlined call.
OMAKEFACE // construct an interface value from rtype/itab and data pointers
OITAB // rtype/itab pointer of an interface value
OIDATA // data pointer of an interface value
OSPTR // base pointer of a slice or string. Bounded==1 means known non-nil.
OCFUNC // reference to c function pointer (not go func value)
OCHECKNIL // emit code to ensure pointer/interface not nil
ORESULT // result of a function call; Xoffset is stack offset
OINLMARK // start of an inlined body, with file/line of caller. Xoffset is an index into the inline tree.
OLINKSYMOFFSET // offset within a name
OJUMPTABLE // A jump table structure for implementing dense expression switches
OINLCALL // intermediary representation of an inlined call.
OMAKEFACE // construct an interface value from rtype/itab and data pointers
OITAB // rtype/itab pointer of an interface value
OIDATA // data pointer of an interface value
OSPTR // base pointer of a slice or string. Bounded==1 means known non-nil.
OCFUNC // reference to c function pointer (not go func value)
OCHECKNIL // emit code to ensure pointer/interface not nil
ORESULT // result of a function call; Xoffset is stack offset
OINLMARK // start of an inlined body, with file/line of caller. Xoffset is an index into the inline tree.
OLINKSYMOFFSET // offset within a name
OJUMPTABLE // A jump table structure for implementing dense expression switches
OINTERFACESWITCH // A type switch with interface cases

// opcodes for generics
ODYNAMICDOTTYPE // x = i.(T) where T is a type parameter (or derived from a type parameter)
Expand Down
46 changes: 46 additions & 0 deletions src/cmd/compile/internal/ir/node_gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 11 additions & 10 deletions src/cmd/compile/internal/ir/op_string.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

37 changes: 37 additions & 0 deletions src/cmd/compile/internal/ir/stmt.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package ir
import (
"cmd/compile/internal/base"
"cmd/compile/internal/types"
"cmd/internal/obj"
"cmd/internal/src"
"go/constant"
)
Expand Down Expand Up @@ -309,6 +310,42 @@ func NewJumpTableStmt(pos src.XPos, idx Node) *JumpTableStmt {
return n
}

// An InterfaceSwitchStmt is used to implement type switches.
// Its semantics are:
//
// if RuntimeType implements Descriptor.Cases[0] {
// Case, Itab = 0, itab<RuntimeType, Descriptor.Cases[0]>
// } else if RuntimeType implements Descriptor.Cases[1] {
// Case, Itab = 1, itab<RuntimeType, Descriptor.Cases[1]>
// ...
// } else if RuntimeType implements Descriptor.Cases[N-1] {
// Case, Itab = N-1, itab<RuntimeType, Descriptor.Cases[N-1]>
// } else {
// Case, Itab = len(cases), nil
// }
// RuntimeType must be a non-nil *runtime._type.
// Descriptor must represent an abi.InterfaceSwitch global variable.
type InterfaceSwitchStmt struct {
miniStmt

Case Node
Itab Node
RuntimeType Node
Descriptor *obj.LSym
}

func NewInterfaceSwitchStmt(pos src.XPos, case_, itab, runtimeType Node, descriptor *obj.LSym) *InterfaceSwitchStmt {
n := &InterfaceSwitchStmt{
Case: case_,
Itab: itab,
RuntimeType: runtimeType,
Descriptor: descriptor,
}
n.pos = pos
n.op = OINTERFACESWITCH
return n
}

// An InlineMarkStmt is a marker placed just before an inlined body.
type InlineMarkStmt struct {
miniStmt
Expand Down
1 change: 1 addition & 0 deletions src/cmd/compile/internal/ir/symtab.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ type symsStruct struct {
GCWriteBarrier [8]*obj.LSym
Goschedguarded *obj.LSym
Growslice *obj.LSym
InterfaceSwitch *obj.LSym
Memmove *obj.LSym
Msanread *obj.LSym
Msanwrite *obj.LSym
Expand Down
10 changes: 10 additions & 0 deletions src/cmd/compile/internal/ssagen/ssa.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ func InitConfig() {
ir.Syms.GCWriteBarrier[7] = typecheck.LookupRuntimeFunc("gcWriteBarrier8")
ir.Syms.Goschedguarded = typecheck.LookupRuntimeFunc("goschedguarded")
ir.Syms.Growslice = typecheck.LookupRuntimeFunc("growslice")
ir.Syms.InterfaceSwitch = typecheck.LookupRuntimeFunc("interfaceSwitch")
ir.Syms.Memmove = typecheck.LookupRuntimeFunc("memmove")
ir.Syms.Msanread = typecheck.LookupRuntimeFunc("msanread")
ir.Syms.Msanwrite = typecheck.LookupRuntimeFunc("msanwrite")
Expand Down Expand Up @@ -2017,6 +2018,15 @@ func (s *state) stmt(n ir.Node) {

s.startBlock(bEnd)

case ir.OINTERFACESWITCH:
n := n.(*ir.InterfaceSwitchStmt)

t := s.expr(n.RuntimeType)
d := s.newValue1A(ssa.OpAddr, s.f.Config.Types.BytePtr, n.Descriptor, s.sb)
r := s.rtcall(ir.Syms.InterfaceSwitch, true, []*types.Type{s.f.Config.Types.Int, s.f.Config.Types.BytePtr}, d, t)
s.assign(n.Case, r[0], false, 0)
s.assign(n.Itab, r[1], false, 0)

case ir.OCHECKNIL:
n := n.(*ir.UnaryExpr)
p := s.expr(n.X)
Expand Down
117 changes: 117 additions & 0 deletions src/cmd/compile/internal/test/switch_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,123 @@ func benchmarkSwitchType(b *testing.B, predictable bool) {
n += 8
}
}
sink = n
}

func BenchmarkSwitchInterfaceTypePredictable(b *testing.B) {
benchmarkSwitchInterfaceType(b, true)
}
func BenchmarkSwitchInterfaceTypeUnpredictable(b *testing.B) {
benchmarkSwitchInterfaceType(b, false)
}

type SI0 interface {
si0()
}
type ST0 struct {
}

func (ST0) si0() {
}

type SI1 interface {
si1()
}
type ST1 struct {
}

func (ST1) si1() {
}

type SI2 interface {
si2()
}
type ST2 struct {
}

func (ST2) si2() {
}

type SI3 interface {
si3()
}
type ST3 struct {
}

func (ST3) si3() {
}

type SI4 interface {
si4()
}
type ST4 struct {
}

func (ST4) si4() {
}

type SI5 interface {
si5()
}
type ST5 struct {
}

func (ST5) si5() {
}

type SI6 interface {
si6()
}
type ST6 struct {
}

func (ST6) si6() {
}

type SI7 interface {
si7()
}
type ST7 struct {
}

func (ST7) si7() {
}

func benchmarkSwitchInterfaceType(b *testing.B, predictable bool) {
a := []any{
ST0{},
ST1{},
ST2{},
ST3{},
ST4{},
ST5{},
ST6{},
ST7{},
}
n := 0
rng := newRNG()
for i := 0; i < b.N; i++ {
rng = rng.next(predictable)
switch a[rng.value()&7].(type) {
case SI0:
n += 1
case SI1:
n += 2
case SI2:
n += 3
case SI3:
n += 4
case SI4:
n += 5
case SI5:
n += 6
case SI6:
n += 7
case SI7:
n += 8
}
}
sink = n
}

// A simple random number generator used to make switches conditionally predictable.
Expand Down
3 changes: 3 additions & 0 deletions src/cmd/compile/internal/typecheck/_builtin/runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,9 @@ func panicdottypeE(have, want, iface *byte)
func panicdottypeI(have, want, iface *byte)
func panicnildottype(want *byte)

// interface switches
func interfaceSwitch(s *byte, t *byte) (int, *byte)

// interface equality. Type/itab pointers are already known to be equal, so
// we only need to pass one.
func ifaceeq(tab *uintptr, x, y unsafe.Pointer) (ret bool)
Expand Down
Loading

0 comments on commit 28f4ea1

Please sign in to comment.