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 { ...@@ -54,7 +54,8 @@ var runtimeDecls = [...]struct {
{"assertE2I2", funcTag, 54}, {"assertE2I2", funcTag, 54},
{"assertI2I", funcTag, 52}, {"assertI2I", funcTag, 52},
{"assertI2I2", funcTag, 54}, {"assertI2I2", funcTag, 54},
{"panicdottype", funcTag, 55}, {"panicdottypeE", funcTag, 55},
{"panicdottypeI", funcTag, 55},
{"panicnildottype", funcTag, 56}, {"panicnildottype", funcTag, 56},
{"ifaceeq", funcTag, 57}, {"ifaceeq", funcTag, 57},
{"efaceeq", funcTag, 57}, {"efaceeq", funcTag, 57},
......
...@@ -67,7 +67,8 @@ func assertE2I(typ *byte, iface any) (ret any) ...@@ -67,7 +67,8 @@ func assertE2I(typ *byte, iface any) (ret any)
func assertE2I2(typ *byte, iface any) (ret any, b bool) func assertE2I2(typ *byte, iface any) (ret any, b bool)
func assertI2I(typ *byte, iface any) (ret any) func assertI2I(typ *byte, iface any) (ret any)
func assertI2I2(typ *byte, iface any) (ret any, b bool) 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 panicnildottype(want *byte)
func ifaceeq(i1 any, i2 any) (ret bool) func ifaceeq(i1 any, i2 any) (ret bool)
......
...@@ -376,7 +376,8 @@ var ( ...@@ -376,7 +376,8 @@ var (
panicslice, panicslice,
panicdivide, panicdivide,
growslice, growslice,
panicdottype, panicdottypeE,
panicdottypeI,
panicnildottype, panicnildottype,
assertE2I, assertE2I,
assertE2I2, assertE2I2,
......
...@@ -306,7 +306,8 @@ func compile(fn *Node) { ...@@ -306,7 +306,8 @@ func compile(fn *Node) {
panicslice = Sysfunc("panicslice") panicslice = Sysfunc("panicslice")
panicdivide = Sysfunc("panicdivide") panicdivide = Sysfunc("panicdivide")
growslice = Sysfunc("growslice") growslice = Sysfunc("growslice")
panicdottype = Sysfunc("panicdottype") panicdottypeE = Sysfunc("panicdottypeE")
panicdottypeI = Sysfunc("panicdottypeI")
panicnildottype = Sysfunc("panicnildottype") panicnildottype = Sysfunc("panicnildottype")
assertE2I = Sysfunc("assertE2I") assertE2I = Sysfunc("assertE2I")
assertE2I2 = Sysfunc("assertE2I2") assertE2I2 = Sysfunc("assertE2I2")
......
...@@ -1411,13 +1411,17 @@ func dumptypestructs() { ...@@ -1411,13 +1411,17 @@ func dumptypestructs() {
// inter *interfacetype // inter *interfacetype
// _type *_type // _type *_type
// link *itab // link *itab
// bad int32 // hash uint32
// unused int32 // bad bool
// inhash bool
// unused [2]byte
// fun [1]uintptr // variable sized // fun [1]uintptr // variable sized
// } // }
o := dsymptr(i.sym, 0, dtypesym(i.itype), 0) o := dsymptr(i.sym, 0, dtypesym(i.itype), 0)
o = dsymptr(i.sym, o, dtypesym(i.t), 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 o += len(imethods(i.itype)) * Widthptr // skip fun method pointers
// at runtime the itab will contain pointers to types, other itabs and // 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. // 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 ...@@ -4013,48 +4013,6 @@ func (s *state) floatToUint(cvttab *f2uCvtTab, n *Node, x *ssa.Value, ft, tt *Ty
return s.variable(n, n.Type) 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. // dottype generates SSA for a type assertion node.
// commaok indicates whether to panic or return a bool. // commaok indicates whether to panic or return a bool.
// If commaok is false, resok will be nil. // If commaok is false, resok will be nil.
...@@ -4157,11 +4115,18 @@ func (s *state) dottype(n *Node, commaok bool) (res, resok *ssa.Value) { ...@@ -4157,11 +4115,18 @@ func (s *state) dottype(n *Node, commaok bool) (res, resok *ssa.Value) {
// Converting to a concrete type. // Converting to a concrete type.
direct := isdirectiface(n.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 { if Debug_typeassert > 0 {
Warnl(n.Pos, "type assertion inlined") 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 tmp *Node // temporary for use with large types
var addr *ssa.Value // address of tmp var addr *ssa.Value // address of tmp
...@@ -4173,9 +4138,7 @@ func (s *state) dottype(n *Node, commaok bool) (res, resok *ssa.Value) { ...@@ -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()) 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, cond := s.newValue2(ssa.OpEqPtr, Types[TBOOL], itab, targetITab)
// then this test is redundant and ifaceType should just branch directly to bFail.
cond := s.newValue2(ssa.OpEqPtr, Types[TBOOL], typ, target)
b := s.endBlock() b := s.endBlock()
b.Kind = ssa.BlockIf b.Kind = ssa.BlockIf
b.SetControl(cond) b.SetControl(cond)
...@@ -4190,7 +4153,11 @@ func (s *state) dottype(n *Node, commaok bool) (res, resok *ssa.Value) { ...@@ -4190,7 +4153,11 @@ func (s *state) dottype(n *Node, commaok bool) (res, resok *ssa.Value) {
// on failure, panic by calling panicdottype // on failure, panic by calling panicdottype
s.startBlock(bFail) s.startBlock(bFail)
taddr := s.newValue1A(ssa.OpAddr, byteptr, &ssa.ExternSymbol{Typ: byteptr, Sym: Linksym(typenamesym(n.Left.Type))}, s.sb) 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 // on success, return data from interface
s.startBlock(bOk) s.startBlock(bOk)
......
...@@ -729,11 +729,13 @@ func (s *typeSwitch) walk(sw *Node) { ...@@ -729,11 +729,13 @@ func (s *typeSwitch) walk(sw *Node) {
// Use a similar strategy for non-empty interfaces. // Use a similar strategy for non-empty interfaces.
// Get interface descriptor word. // 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. // Check for nil first.
i := nod(OIF, nil, nil) i := nod(OIF, nil, nil)
i.Left = nod(OEQ, typ, nodnil()) i.Left = nod(OEQ, itab, nodnil())
if clauses.niljmp != nil { if clauses.niljmp != nil {
// Do explicit nil case right here. // Do explicit nil case right here.
i.Nbody.Set1(clauses.niljmp) i.Nbody.Set1(clauses.niljmp)
...@@ -749,15 +751,15 @@ func (s *typeSwitch) walk(sw *Node) { ...@@ -749,15 +751,15 @@ func (s *typeSwitch) walk(sw *Node) {
i.Left = typecheck(i.Left, Erv) i.Left = typecheck(i.Left, Erv)
cas = append(cas, i) cas = append(cas, i)
if !cond.Right.Type.IsEmptyInterface() { // Load hash from type or itab.
// Load type from itab. h := nodSym(ODOTPTR, itab, nil)
typ = itabType(typ)
}
// Load hash from type.
h := nodSym(ODOTPTR, typ, nil)
h.Type = Types[TUINT32] h.Type = Types[TUINT32]
h.Typecheck = 1 h.Typecheck = 1
if cond.Right.Type.IsEmptyInterface() {
h.Xoffset = int64(2 * Widthptr) // offset of hash in runtime._type 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 h.Bounded = true // guaranteed not to fault
a = nod(OAS, s.hashname, h) a = nod(OAS, s.hashname, h)
a = typecheck(a, Etop) a = typecheck(a, Etop)
......
...@@ -53,7 +53,7 @@ func getitab(inter *interfacetype, typ *_type, canfail bool) *itab { ...@@ -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 { for m = (*itab)(atomic.Loadp(unsafe.Pointer(&hash[h]))); m != nil; m = m.link {
if m.inter == inter && m._type == typ { if m.inter == inter && m._type == typ {
if m.bad != 0 { if m.bad {
if !canfail { if !canfail {
// this can only happen if the conversion // this can only happen if the conversion
// was already done once using the , ok form // was already done once using the , ok form
...@@ -78,7 +78,7 @@ func getitab(inter *interfacetype, typ *_type, canfail bool) *itab { ...@@ -78,7 +78,7 @@ func getitab(inter *interfacetype, typ *_type, canfail bool) *itab {
m._type = typ m._type = typ
additab(m, true, canfail) additab(m, true, canfail)
unlock(&ifaceLock) unlock(&ifaceLock)
if m.bad != 0 { if m.bad {
return nil return nil
} }
return m return m
...@@ -130,7 +130,7 @@ func additab(m *itab, locked, canfail bool) { ...@@ -130,7 +130,7 @@ func additab(m *itab, locked, canfail bool) {
} }
panic(&TypeAssertionError{"", typ.string(), inter.typ.string(), iname}) panic(&TypeAssertionError{"", typ.string(), inter.typ.string(), iname})
} }
m.bad = 1 m.bad = true
break break
nextimethod: nextimethod:
} }
...@@ -139,7 +139,7 @@ func additab(m *itab, locked, canfail bool) { ...@@ -139,7 +139,7 @@ func additab(m *itab, locked, canfail bool) {
} }
h := itabhash(inter, typ) h := itabhash(inter, typ)
m.link = hash[h] m.link = hash[h]
m.inhash = 1 m.inhash = true
atomicstorep(unsafe.Pointer(&hash[h]), unsafe.Pointer(m)) atomicstorep(unsafe.Pointer(&hash[h]), unsafe.Pointer(m))
} }
...@@ -152,7 +152,7 @@ func itabsinit() { ...@@ -152,7 +152,7 @@ func itabsinit() {
// and thanks to the way global symbol resolution works, the // and thanks to the way global symbol resolution works, the
// pointed-to itab may already have been inserted into the // pointed-to itab may already have been inserted into the
// global 'hash'. // global 'hash'.
if i.inhash == 0 { if !i.inhash {
additab(i, true, false) additab(i, true, false)
} }
} }
...@@ -160,11 +160,11 @@ func itabsinit() { ...@@ -160,11 +160,11 @@ func itabsinit() {
unlock(&ifaceLock) 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. // have = the dynamic type we have.
// want = the static type we're trying to convert to. // want = the static type we're trying to convert to.
// iface = the static type we're converting from. // iface = the static type we're converting from.
func panicdottype(have, want, iface *_type) { func panicdottypeE(have, want, iface *_type) {
haveString := "" haveString := ""
if have != nil { if have != nil {
haveString = have.string() haveString = have.string()
...@@ -172,6 +172,16 @@ func panicdottype(have, want, iface *_type) { ...@@ -172,6 +172,16 @@ func panicdottype(have, want, iface *_type) {
panic(&TypeAssertionError{iface.string(), haveString, want.string(), ""}) 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. // 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. // want = the static type we're trying to convert to.
func panicnildottype(want *_type) { func panicnildottype(want *_type) {
......
...@@ -56,7 +56,7 @@ func plugin_lastmoduleinit() (path string, syms map[string]interface{}, mismatch ...@@ -56,7 +56,7 @@ func plugin_lastmoduleinit() (path string, syms map[string]interface{}, mismatch
lock(&ifaceLock) lock(&ifaceLock)
for _, i := range md.itablinks { for _, i := range md.itablinks {
if i.inhash == 0 { if !i.inhash {
additab(i, true, false) additab(i, true, false)
} }
} }
......
...@@ -644,8 +644,10 @@ type itab struct { ...@@ -644,8 +644,10 @@ type itab struct {
inter *interfacetype inter *interfacetype
_type *_type _type *_type
link *itab link *itab
bad int32 hash uint32 // copy of _type.hash. Used for type switches.
inhash int32 // has this itab been added to hash? bad bool // type does not implement interface
inhash bool // has this itab been added to hash?
unused [2]byte
fun [1]uintptr // variable sized 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