Commit 5a75d6a0 authored by Keith Randall's avatar Keith Randall

cmd/compile: optimize non-empty-interface type conversions

When doing i.(T) for non-empty-interface i and concrete type T,
there's no need to read the type out of the itab. Just compare the
itab to the itab we expect for that interface/type pair.

Also optimize type switches by putting the type hash of the
concrete type in the itab. That way we don't need to load the
type pointer out of the itab.

Update #18492

Change-Id: I49e280a21e5687e771db5b8a56b685291ac168ce
Reviewed-on: https://go-review.googlesource.com/34810
Run-TryBot: Keith Randall <khr@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: 's avatarJosh Bleecher Snyder <josharian@gmail.com>
Reviewed-by: 's avatarDavid Chase <drchase@google.com>
parent ee2f5faf
......@@ -54,7 +54,8 @@ var runtimeDecls = [...]struct {
{"assertE2I2", funcTag, 54},
{"assertI2I", funcTag, 52},
{"assertI2I2", funcTag, 54},
{"panicdottype", funcTag, 55},
{"panicdottypeE", funcTag, 55},
{"panicdottypeI", funcTag, 55},
{"panicnildottype", funcTag, 56},
{"ifaceeq", funcTag, 57},
{"efaceeq", funcTag, 57},
......
......@@ -67,7 +67,8 @@ func assertE2I(typ *byte, iface any) (ret any)
func assertE2I2(typ *byte, iface any) (ret any, b bool)
func assertI2I(typ *byte, iface any) (ret any)
func assertI2I2(typ *byte, iface any) (ret any, b bool)
func panicdottype(have, want, iface *byte)
func panicdottypeE(have, want, iface *byte)
func panicdottypeI(have, want, iface *byte)
func panicnildottype(want *byte)
func ifaceeq(i1 any, i2 any) (ret bool)
......
......@@ -376,7 +376,8 @@ var (
panicslice,
panicdivide,
growslice,
panicdottype,
panicdottypeE,
panicdottypeI,
panicnildottype,
assertE2I,
assertE2I2,
......
......@@ -306,7 +306,8 @@ func compile(fn *Node) {
panicslice = Sysfunc("panicslice")
panicdivide = Sysfunc("panicdivide")
growslice = Sysfunc("growslice")
panicdottype = Sysfunc("panicdottype")
panicdottypeE = Sysfunc("panicdottypeE")
panicdottypeI = Sysfunc("panicdottypeI")
panicnildottype = Sysfunc("panicnildottype")
assertE2I = Sysfunc("assertE2I")
assertE2I2 = Sysfunc("assertE2I2")
......
......@@ -1411,13 +1411,17 @@ func dumptypestructs() {
// inter *interfacetype
// _type *_type
// link *itab
// bad int32
// unused int32
// hash uint32
// bad bool
// inhash bool
// unused [2]byte
// fun [1]uintptr // variable sized
// }
o := dsymptr(i.sym, 0, dtypesym(i.itype), 0)
o = dsymptr(i.sym, o, dtypesym(i.t), 0)
o += Widthptr + 8 // skip link/bad/inhash fields
o += Widthptr // skip link field
o = duint32(i.sym, o, typehash(i.t)) // copy of type hash
o += 4 // skip bad/inhash/unused fields
o += len(imethods(i.itype)) * Widthptr // skip fun method pointers
// at runtime the itab will contain pointers to types, other itabs and
// method functions. None are allocated on heap, so we can use obj.NOPTR.
......
......@@ -4013,48 +4013,6 @@ func (s *state) floatToUint(cvttab *f2uCvtTab, n *Node, x *ssa.Value, ft, tt *Ty
return s.variable(n, n.Type)
}
// ifaceType returns the value for the word containing the type.
// t is the type of the interface expression.
// v is the corresponding value.
func (s *state) ifaceType(t *Type, v *ssa.Value) *ssa.Value {
byteptr := ptrto(Types[TUINT8]) // type used in runtime prototypes for runtime type (*byte)
if t.IsEmptyInterface() {
// Have eface. The type is the first word in the struct.
return s.newValue1(ssa.OpITab, byteptr, v)
}
// Have iface.
// The first word in the struct is the itab.
// If the itab is nil, return 0.
// Otherwise, the second word in the itab is the type.
tab := s.newValue1(ssa.OpITab, byteptr, v)
s.vars[&typVar] = tab
isnonnil := s.newValue2(ssa.OpNeqPtr, Types[TBOOL], tab, s.constNil(byteptr))
b := s.endBlock()
b.Kind = ssa.BlockIf
b.SetControl(isnonnil)
b.Likely = ssa.BranchLikely
bLoad := s.f.NewBlock(ssa.BlockPlain)
bEnd := s.f.NewBlock(ssa.BlockPlain)
b.AddEdgeTo(bLoad)
b.AddEdgeTo(bEnd)
bLoad.AddEdgeTo(bEnd)
s.startBlock(bLoad)
off := s.newValue1I(ssa.OpOffPtr, byteptr, int64(Widthptr), tab)
s.vars[&typVar] = s.newValue2(ssa.OpLoad, byteptr, off, s.mem())
s.endBlock()
s.startBlock(bEnd)
typ := s.variable(&typVar, byteptr)
delete(s.vars, &typVar)
return typ
}
// dottype generates SSA for a type assertion node.
// commaok indicates whether to panic or return a bool.
// If commaok is false, resok will be nil.
......@@ -4157,11 +4115,18 @@ func (s *state) dottype(n *Node, commaok bool) (res, resok *ssa.Value) {
// Converting to a concrete type.
direct := isdirectiface(n.Type)
typ := s.ifaceType(n.Left.Type, iface) // actual concrete type of input interface
itab := s.newValue1(ssa.OpITab, byteptr, iface) // type word of interface
if Debug_typeassert > 0 {
Warnl(n.Pos, "type assertion inlined")
}
var targetITab *ssa.Value
if n.Left.Type.IsEmptyInterface() {
// Looking for pointer to target type.
targetITab = target
} else {
// Looking for pointer to itab for target type and source interface.
targetITab = s.expr(itabname(n.Type, n.Left.Type))
}
var tmp *Node // temporary for use with large types
var addr *ssa.Value // address of tmp
......@@ -4173,9 +4138,7 @@ func (s *state) dottype(n *Node, commaok bool) (res, resok *ssa.Value) {
s.vars[&memVar] = s.newValue1A(ssa.OpVarDef, ssa.TypeMem, tmp, s.mem())
}
// TODO: If we have a nonempty interface and its itab field is nil,
// then this test is redundant and ifaceType should just branch directly to bFail.
cond := s.newValue2(ssa.OpEqPtr, Types[TBOOL], typ, target)
cond := s.newValue2(ssa.OpEqPtr, Types[TBOOL], itab, targetITab)
b := s.endBlock()
b.Kind = ssa.BlockIf
b.SetControl(cond)
......@@ -4190,7 +4153,11 @@ func (s *state) dottype(n *Node, commaok bool) (res, resok *ssa.Value) {
// on failure, panic by calling panicdottype
s.startBlock(bFail)
taddr := s.newValue1A(ssa.OpAddr, byteptr, &ssa.ExternSymbol{Typ: byteptr, Sym: Linksym(typenamesym(n.Left.Type))}, s.sb)
s.rtcall(panicdottype, false, nil, typ, target, taddr)
if n.Left.Type.IsEmptyInterface() {
s.rtcall(panicdottypeE, false, nil, itab, target, taddr)
} else {
s.rtcall(panicdottypeI, false, nil, itab, target, taddr)
}
// on success, return data from interface
s.startBlock(bOk)
......
......@@ -729,11 +729,13 @@ func (s *typeSwitch) walk(sw *Node) {
// Use a similar strategy for non-empty interfaces.
// Get interface descriptor word.
typ := nod(OITAB, s.facename, nil)
// For empty interfaces this will be the type.
// For non-empty interfaces this will be the itab.
itab := nod(OITAB, s.facename, nil)
// Check for nil first.
i := nod(OIF, nil, nil)
i.Left = nod(OEQ, typ, nodnil())
i.Left = nod(OEQ, itab, nodnil())
if clauses.niljmp != nil {
// Do explicit nil case right here.
i.Nbody.Set1(clauses.niljmp)
......@@ -749,16 +751,16 @@ func (s *typeSwitch) walk(sw *Node) {
i.Left = typecheck(i.Left, Erv)
cas = append(cas, i)
if !cond.Right.Type.IsEmptyInterface() {
// Load type from itab.
typ = itabType(typ)
}
// Load hash from type.
h := nodSym(ODOTPTR, typ, nil)
// Load hash from type or itab.
h := nodSym(ODOTPTR, itab, nil)
h.Type = Types[TUINT32]
h.Typecheck = 1
h.Xoffset = int64(2 * Widthptr) // offset of hash in runtime._type
h.Bounded = true // guaranteed not to fault
if cond.Right.Type.IsEmptyInterface() {
h.Xoffset = int64(2 * Widthptr) // offset of hash in runtime._type
} else {
h.Xoffset = int64(3 * Widthptr) // offset of hash in runtime.itab
}
h.Bounded = true // guaranteed not to fault
a = nod(OAS, s.hashname, h)
a = typecheck(a, Etop)
cas = append(cas, a)
......
......@@ -53,7 +53,7 @@ func getitab(inter *interfacetype, typ *_type, canfail bool) *itab {
}
for m = (*itab)(atomic.Loadp(unsafe.Pointer(&hash[h]))); m != nil; m = m.link {
if m.inter == inter && m._type == typ {
if m.bad != 0 {
if m.bad {
if !canfail {
// this can only happen if the conversion
// was already done once using the , ok form
......@@ -78,7 +78,7 @@ func getitab(inter *interfacetype, typ *_type, canfail bool) *itab {
m._type = typ
additab(m, true, canfail)
unlock(&ifaceLock)
if m.bad != 0 {
if m.bad {
return nil
}
return m
......@@ -130,7 +130,7 @@ func additab(m *itab, locked, canfail bool) {
}
panic(&TypeAssertionError{"", typ.string(), inter.typ.string(), iname})
}
m.bad = 1
m.bad = true
break
nextimethod:
}
......@@ -139,7 +139,7 @@ func additab(m *itab, locked, canfail bool) {
}
h := itabhash(inter, typ)
m.link = hash[h]
m.inhash = 1
m.inhash = true
atomicstorep(unsafe.Pointer(&hash[h]), unsafe.Pointer(m))
}
......@@ -152,7 +152,7 @@ func itabsinit() {
// and thanks to the way global symbol resolution works, the
// pointed-to itab may already have been inserted into the
// global 'hash'.
if i.inhash == 0 {
if !i.inhash {
additab(i, true, false)
}
}
......@@ -160,11 +160,11 @@ func itabsinit() {
unlock(&ifaceLock)
}
// panicdottype is called when doing an i.(T) conversion and the conversion fails.
// panicdottypeE is called when doing an e.(T) conversion and the conversion fails.
// have = the dynamic type we have.
// want = the static type we're trying to convert to.
// iface = the static type we're converting from.
func panicdottype(have, want, iface *_type) {
func panicdottypeE(have, want, iface *_type) {
haveString := ""
if have != nil {
haveString = have.string()
......@@ -172,6 +172,16 @@ func panicdottype(have, want, iface *_type) {
panic(&TypeAssertionError{iface.string(), haveString, want.string(), ""})
}
// panicdottypeI is called when doing an i.(T) conversion and the conversion fails.
// Same args as panicdottypeE, but "have" is the dynamic itab we have.
func panicdottypeI(have *itab, want, iface *_type) {
var t *_type
if have != nil {
t = have._type
}
panicdottypeE(t, want, iface)
}
// panicnildottype is called when doing a i.(T) conversion and the interface i is nil.
// want = the static type we're trying to convert to.
func panicnildottype(want *_type) {
......
......@@ -56,7 +56,7 @@ func plugin_lastmoduleinit() (path string, syms map[string]interface{}, mismatch
lock(&ifaceLock)
for _, i := range md.itablinks {
if i.inhash == 0 {
if !i.inhash {
additab(i, true, false)
}
}
......
......@@ -644,8 +644,10 @@ type itab struct {
inter *interfacetype
_type *_type
link *itab
bad int32
inhash int32 // has this itab been added to hash?
hash uint32 // copy of _type.hash. Used for type switches.
bad bool // type does not implement interface
inhash bool // has this itab been added to hash?
unused [2]byte
fun [1]uintptr // variable sized
}
......
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