From: Keith Randall Date: Wed, 4 Jan 2017 00:15:38 +0000 (-0800) Subject: cmd/compile: optimize non-empty-interface type conversions X-Git-Tag: go1.9beta1~1598 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=5a75d6a08ebb727c0e2bf5c8fbcbc104d260d302;p=gostls13.git 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 TryBot-Result: Gobot Gobot Reviewed-by: Josh Bleecher Snyder Reviewed-by: David Chase --- diff --git a/src/cmd/compile/internal/gc/builtin.go b/src/cmd/compile/internal/gc/builtin.go index 70200c624b..47dcf0bb4b 100644 --- a/src/cmd/compile/internal/gc/builtin.go +++ b/src/cmd/compile/internal/gc/builtin.go @@ -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}, diff --git a/src/cmd/compile/internal/gc/builtin/runtime.go b/src/cmd/compile/internal/gc/builtin/runtime.go index e9d41a3095..618f1c421e 100644 --- a/src/cmd/compile/internal/gc/builtin/runtime.go +++ b/src/cmd/compile/internal/gc/builtin/runtime.go @@ -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) diff --git a/src/cmd/compile/internal/gc/go.go b/src/cmd/compile/internal/gc/go.go index c6fcfd7347..9e5d1843d0 100644 --- a/src/cmd/compile/internal/gc/go.go +++ b/src/cmd/compile/internal/gc/go.go @@ -376,7 +376,8 @@ var ( panicslice, panicdivide, growslice, - panicdottype, + panicdottypeE, + panicdottypeI, panicnildottype, assertE2I, assertE2I2, diff --git a/src/cmd/compile/internal/gc/pgen.go b/src/cmd/compile/internal/gc/pgen.go index 1acbbf3b1e..e612cf6a33 100644 --- a/src/cmd/compile/internal/gc/pgen.go +++ b/src/cmd/compile/internal/gc/pgen.go @@ -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") diff --git a/src/cmd/compile/internal/gc/reflect.go b/src/cmd/compile/internal/gc/reflect.go index 9d744c6a96..b6bda3c86b 100644 --- a/src/cmd/compile/internal/gc/reflect.go +++ b/src/cmd/compile/internal/gc/reflect.go @@ -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. diff --git a/src/cmd/compile/internal/gc/ssa.go b/src/cmd/compile/internal/gc/ssa.go index ca198575d1..6871a9eed8 100644 --- a/src/cmd/compile/internal/gc/ssa.go +++ b/src/cmd/compile/internal/gc/ssa.go @@ -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) diff --git a/src/cmd/compile/internal/gc/swt.go b/src/cmd/compile/internal/gc/swt.go index 8f6ffa2690..f48894d77b 100644 --- a/src/cmd/compile/internal/gc/swt.go +++ b/src/cmd/compile/internal/gc/swt.go @@ -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) diff --git a/src/runtime/iface.go b/src/runtime/iface.go index b5c31a301d..f043724a56 100644 --- a/src/runtime/iface.go +++ b/src/runtime/iface.go @@ -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) { diff --git a/src/runtime/plugin.go b/src/runtime/plugin.go index 8edb29c9fe..ea246509cc 100644 --- a/src/runtime/plugin.go +++ b/src/runtime/plugin.go @@ -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) } } diff --git a/src/runtime/runtime2.go b/src/runtime/runtime2.go index 9cb2b85f33..8cf13e96d8 100644 --- a/src/runtime/runtime2.go +++ b/src/runtime/runtime2.go @@ -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 }