1
0
mirror of https://github.com/golang/go synced 2024-11-19 16:44:43 -07:00
go/src/runtime/iface.go

428 lines
8.7 KiB
Go
Raw Normal View History

// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package runtime
import (
"runtime/internal/atomic"
"runtime/internal/sys"
"unsafe"
)
const (
hashSize = 1009
)
var (
ifaceLock mutex // lock for accessing hash
hash [hashSize]*itab
)
func itabhash(inter *interfacetype, typ *_type) uint32 {
// compiler has provided some good hash codes for us.
h := inter.typ.hash
h += 17 * typ.hash
// TODO(rsc): h += 23 * x.mhash ?
return h % hashSize
}
func getitab(inter *interfacetype, typ *_type, canfail bool) *itab {
if len(inter.mhdr) == 0 {
throw("internal error - misuse of itab")
}
// easy case
if typ.tflag&tflagUncommon == 0 {
if canfail {
return nil
}
panic(&TypeAssertionError{"", typ._string, inter.typ._string, inter.mhdr[0].name.name()})
}
h := itabhash(inter, typ)
// look twice - once without lock, once with.
// common case will be no lock contention.
var m *itab
var locked int
for locked = 0; locked < 2; locked++ {
if locked != 0 {
lock(&ifaceLock)
}
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 {
m = nil
if !canfail {
// this can only happen if the conversion
// was already done once using the , ok form
// and we have a cached negative result.
// the cached result doesn't record which
// interface function was missing, so try
// adding the itab again, which will throw an error.
additab(m, locked != 0, false)
}
}
if locked != 0 {
unlock(&ifaceLock)
}
return m
}
}
}
m = (*itab)(persistentalloc(unsafe.Sizeof(itab{})+uintptr(len(inter.mhdr)-1)*sys.PtrSize, 0, &memstats.other_sys))
m.inter = inter
m._type = typ
additab(m, true, canfail)
unlock(&ifaceLock)
if m.bad != 0 {
return nil
}
return m
}
func additab(m *itab, locked, canfail bool) {
inter := m.inter
typ := m._type
x := typ.uncommon()
// both inter and typ have method sorted by name,
// and interface names are unique,
// so can iterate over both in lock step;
// the loop is O(ni+nt) not O(ni*nt).
ni := len(inter.mhdr)
cmd/compile, etc: store method tables as offsets This CL introduces the typeOff type and a lookup method of the same name that can turn a typeOff offset into an *rtype. In a typical Go binary (built with buildmode=exe, pie, c-archive, or c-shared), there is one moduledata and all typeOff values are offsets relative to firstmoduledata.types. This makes computing the pointer cheap in typical programs. With buildmode=shared (and one day, buildmode=plugin) there are multiple modules whose relative offset is determined at runtime. We identify a type in the general case by the pair of the original *rtype that references it and its typeOff value. We determine the module from the original pointer, and then use the typeOff from there to compute the final *rtype. To ensure there is only one *rtype representing each type, the runtime initializes a typemap for each module, using any identical type from an earlier module when resolving that offset. This means that types computed from an offset match the type mapped by the pointer dynamic relocations. A series of followup CLs will replace other *rtype values with typeOff (and name/*string with nameOff). For types created at runtime by reflect, type offsets are treated as global IDs and reference into a reflect offset map kept by the runtime. darwin/amd64: cmd/go: -57KB (0.6%) jujud: -557KB (0.8%) linux/amd64 PIE: cmd/go: -361KB (3.0%) jujud: -3.5MB (4.2%) For #6853. Change-Id: Icf096fd884a0a0cb9f280f46f7a26c70a9006c96 Reviewed-on: https://go-review.googlesource.com/21285 Reviewed-by: Ian Lance Taylor <iant@golang.org> Run-TryBot: David Crawshaw <crawshaw@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org>
2016-03-28 08:32:27 -06:00
nt := int(x.mcount)
xmhdr := (*[1 << 16]method)(add(unsafe.Pointer(x), uintptr(x.moff)))[:nt:nt]
j := 0
for k := 0; k < ni; k++ {
i := &inter.mhdr[k]
iname := i.name.name()
itype := i._type
ipkg := i.name.pkgPath()
if ipkg == "" {
ipkg = inter.pkgpath.name()
}
for ; j < nt; j++ {
cmd/compile, etc: store method tables as offsets This CL introduces the typeOff type and a lookup method of the same name that can turn a typeOff offset into an *rtype. In a typical Go binary (built with buildmode=exe, pie, c-archive, or c-shared), there is one moduledata and all typeOff values are offsets relative to firstmoduledata.types. This makes computing the pointer cheap in typical programs. With buildmode=shared (and one day, buildmode=plugin) there are multiple modules whose relative offset is determined at runtime. We identify a type in the general case by the pair of the original *rtype that references it and its typeOff value. We determine the module from the original pointer, and then use the typeOff from there to compute the final *rtype. To ensure there is only one *rtype representing each type, the runtime initializes a typemap for each module, using any identical type from an earlier module when resolving that offset. This means that types computed from an offset match the type mapped by the pointer dynamic relocations. A series of followup CLs will replace other *rtype values with typeOff (and name/*string with nameOff). For types created at runtime by reflect, type offsets are treated as global IDs and reference into a reflect offset map kept by the runtime. darwin/amd64: cmd/go: -57KB (0.6%) jujud: -557KB (0.8%) linux/amd64 PIE: cmd/go: -361KB (3.0%) jujud: -3.5MB (4.2%) For #6853. Change-Id: Icf096fd884a0a0cb9f280f46f7a26c70a9006c96 Reviewed-on: https://go-review.googlesource.com/21285 Reviewed-by: Ian Lance Taylor <iant@golang.org> Run-TryBot: David Crawshaw <crawshaw@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org>
2016-03-28 08:32:27 -06:00
t := &xmhdr[j]
if typ.typeOff(t.mtyp) == itype && t.name.name() == iname {
pkgPath := t.name.pkgPath()
if pkgPath == "" {
pkgPath = x.pkgpath.name()
}
if t.name.isExported() || pkgPath == ipkg {
if m != nil {
cmd/compile, etc: store method tables as offsets This CL introduces the typeOff type and a lookup method of the same name that can turn a typeOff offset into an *rtype. In a typical Go binary (built with buildmode=exe, pie, c-archive, or c-shared), there is one moduledata and all typeOff values are offsets relative to firstmoduledata.types. This makes computing the pointer cheap in typical programs. With buildmode=shared (and one day, buildmode=plugin) there are multiple modules whose relative offset is determined at runtime. We identify a type in the general case by the pair of the original *rtype that references it and its typeOff value. We determine the module from the original pointer, and then use the typeOff from there to compute the final *rtype. To ensure there is only one *rtype representing each type, the runtime initializes a typemap for each module, using any identical type from an earlier module when resolving that offset. This means that types computed from an offset match the type mapped by the pointer dynamic relocations. A series of followup CLs will replace other *rtype values with typeOff (and name/*string with nameOff). For types created at runtime by reflect, type offsets are treated as global IDs and reference into a reflect offset map kept by the runtime. darwin/amd64: cmd/go: -57KB (0.6%) jujud: -557KB (0.8%) linux/amd64 PIE: cmd/go: -361KB (3.0%) jujud: -3.5MB (4.2%) For #6853. Change-Id: Icf096fd884a0a0cb9f280f46f7a26c70a9006c96 Reviewed-on: https://go-review.googlesource.com/21285 Reviewed-by: Ian Lance Taylor <iant@golang.org> Run-TryBot: David Crawshaw <crawshaw@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org>
2016-03-28 08:32:27 -06:00
ifn := typ.textOff(t.ifn)
*(*unsafe.Pointer)(add(unsafe.Pointer(&m.fun[0]), uintptr(k)*sys.PtrSize)) = ifn
}
goto nextimethod
}
}
}
// didn't find method
if !canfail {
if locked {
unlock(&ifaceLock)
}
panic(&TypeAssertionError{"", typ._string, inter.typ._string, iname})
}
m.bad = 1
break
nextimethod:
}
if !locked {
throw("invalid itab locking")
}
h := itabhash(inter, typ)
m.link = hash[h]
atomicstorep(unsafe.Pointer(&hash[h]), unsafe.Pointer(m))
}
func itabsinit() {
lock(&ifaceLock)
for m := &firstmoduledata; m != nil; m = m.next {
for _, i := range m.itablinks {
additab(i, true, false)
}
}
unlock(&ifaceLock)
}
func convT2E(t *_type, elem unsafe.Pointer, x unsafe.Pointer) (e eface) {
if raceenabled {
raceReadObjectPC(t, elem, getcallerpc(unsafe.Pointer(&t)), funcPC(convT2E))
}
if msanenabled {
msanread(elem, t.size)
}
if isDirectIface(t) {
e._type = t
typedmemmove(t, unsafe.Pointer(&e.data), elem)
} else {
if x == nil {
x = newobject(t)
}
// TODO: We allocate a zeroed object only to overwrite it with
// actual data. Figure out how to avoid zeroing. Also below in convT2I.
typedmemmove(t, x, elem)
e._type = t
e.data = x
}
return
}
cmd/compile: optimize remaining convT2I calls See #14874 Updates #6853 This change adds a compiler optimization for non pointer shaped convT2I. Since itab symbols are now emitted by the compiler, the itab address can be passed directly to convT2I instead of passing the iface type and a cache pointer argument. Compilebench results for the 5-commits series ending here: name old time/op new time/op delta Template 336ms ± 4% 344ms ± 4% +2.61% (p=0.027 n=9+8) Unicode 165ms ± 6% 173ms ± 7% +5.11% (p=0.014 n=9+9) GoTypes 1.09s ± 1% 1.06s ± 2% -3.29% (p=0.000 n=9+9) Compiler 5.09s ±10% 4.75s ±10% -6.64% (p=0.011 n=10+10) MakeBash 31.1s ± 5% 30.3s ± 3% ~ (p=0.089 n=10+10) name old text-bytes new text-bytes delta HelloSize 558k ± 0% 558k ± 0% +0.02% (p=0.000 n=10+10) CmdGoSize 6.24M ± 0% 6.11M ± 0% -2.11% (p=0.000 n=10+10) name old data-bytes new data-bytes delta HelloSize 3.66k ± 0% 3.74k ± 0% +2.41% (p=0.000 n=10+10) CmdGoSize 134k ± 0% 162k ± 0% +20.76% (p=0.000 n=10+10) name old bss-bytes new bss-bytes delta HelloSize 126k ± 0% 126k ± 0% ~ (all samples are equal) CmdGoSize 149k ± 0% 146k ± 0% -2.17% (p=0.000 n=10+10) name old exe-bytes new exe-bytes delta HelloSize 924k ± 0% 924k ± 0% +0.05% (p=0.000 n=10+10) CmdGoSize 9.77M ± 0% 9.62M ± 0% -1.47% (p=0.000 n=10+10) Change-Id: Ib230ddc04988824035c32287ae544a965fedd344 Reviewed-on: https://go-review.googlesource.com/20902 Reviewed-by: Keith Randall <khr@golang.org> Reviewed-by: David Crawshaw <crawshaw@golang.org> Run-TryBot: Michel Lespinasse <walken@google.com>
2016-03-18 18:21:33 -06:00
func convT2I(tab *itab, elem unsafe.Pointer, x unsafe.Pointer) (i iface) {
t := tab._type
if raceenabled {
raceReadObjectPC(t, elem, getcallerpc(unsafe.Pointer(&tab)), funcPC(convT2I))
}
if msanenabled {
msanread(elem, t.size)
}
if isDirectIface(t) {
i.tab = tab
typedmemmove(t, unsafe.Pointer(&i.data), elem)
} else {
if x == nil {
x = newobject(t)
}
typedmemmove(t, x, elem)
i.tab = tab
i.data = x
}
return
}
func panicdottype(have, want, iface *_type) {
haveString := ""
if have != nil {
haveString = have._string
}
panic(&TypeAssertionError{iface._string, haveString, want._string, ""})
}
func assertI2T(t *_type, i iface, r unsafe.Pointer) {
tab := i.tab
if tab == nil {
panic(&TypeAssertionError{"", "", t._string, ""})
}
if tab._type != t {
panic(&TypeAssertionError{tab.inter.typ._string, tab._type._string, t._string, ""})
}
if r != nil {
if isDirectIface(t) {
writebarrierptr((*uintptr)(r), uintptr(i.data))
} else {
typedmemmove(t, r, i.data)
}
}
}
func assertI2T2(t *_type, i iface, r unsafe.Pointer) bool {
tab := i.tab
if tab == nil || tab._type != t {
if r != nil {
memclr(r, t.size)
}
return false
}
if r != nil {
if isDirectIface(t) {
writebarrierptr((*uintptr)(r), uintptr(i.data))
} else {
typedmemmove(t, r, i.data)
}
}
return true
}
func assertE2T(t *_type, e eface, r unsafe.Pointer) {
if e._type == nil {
panic(&TypeAssertionError{"", "", t._string, ""})
}
if e._type != t {
panic(&TypeAssertionError{"", e._type._string, t._string, ""})
}
if r != nil {
if isDirectIface(t) {
writebarrierptr((*uintptr)(r), uintptr(e.data))
} else {
typedmemmove(t, r, e.data)
}
}
}
var testingAssertE2T2GC bool
// The compiler ensures that r is non-nil.
func assertE2T2(t *_type, e eface, r unsafe.Pointer) bool {
if testingAssertE2T2GC {
GC()
}
if e._type != t {
memclr(r, t.size)
return false
}
if isDirectIface(t) {
writebarrierptr((*uintptr)(r), uintptr(e.data))
} else {
typedmemmove(t, r, e.data)
}
return true
}
func convI2E(i iface) (r eface) {
tab := i.tab
if tab == nil {
return
}
r._type = tab._type
r.data = i.data
return
}
func assertI2E(inter *interfacetype, i iface, r *eface) {
tab := i.tab
if tab == nil {
// explicit conversions require non-nil interface value.
panic(&TypeAssertionError{"", "", inter.typ._string, ""})
}
r._type = tab._type
r.data = i.data
return
}
// The compiler ensures that r is non-nil.
func assertI2E2(inter *interfacetype, i iface, r *eface) bool {
tab := i.tab
if tab == nil {
return false
}
r._type = tab._type
r.data = i.data
return true
}
func convI2I(inter *interfacetype, i iface) (r iface) {
tab := i.tab
if tab == nil {
return
}
if tab.inter == inter {
r.tab = tab
r.data = i.data
return
}
r.tab = getitab(inter, tab._type, false)
r.data = i.data
return
}
func assertI2I(inter *interfacetype, i iface, r *iface) {
tab := i.tab
if tab == nil {
// explicit conversions require non-nil interface value.
panic(&TypeAssertionError{"", "", inter.typ._string, ""})
}
if tab.inter == inter {
r.tab = tab
r.data = i.data
return
}
r.tab = getitab(inter, tab._type, false)
r.data = i.data
}
func assertI2I2(inter *interfacetype, i iface, r *iface) bool {
tab := i.tab
if tab == nil {
if r != nil {
*r = iface{}
}
return false
}
if tab.inter != inter {
tab = getitab(inter, tab._type, true)
if tab == nil {
if r != nil {
*r = iface{}
}
return false
}
}
if r != nil {
r.tab = tab
r.data = i.data
}
return true
}
func assertE2I(inter *interfacetype, e eface, r *iface) {
t := e._type
if t == nil {
// explicit conversions require non-nil interface value.
panic(&TypeAssertionError{"", "", inter.typ._string, ""})
}
r.tab = getitab(inter, t, false)
r.data = e.data
}
var testingAssertE2I2GC bool
func assertE2I2(inter *interfacetype, e eface, r *iface) bool {
if testingAssertE2I2GC {
GC()
}
t := e._type
if t == nil {
if r != nil {
*r = iface{}
}
return false
}
tab := getitab(inter, t, true)
if tab == nil {
if r != nil {
*r = iface{}
}
return false
}
if r != nil {
r.tab = tab
r.data = e.data
}
return true
}
//go:linkname reflect_ifaceE2I reflect.ifaceE2I
func reflect_ifaceE2I(inter *interfacetype, e eface, dst *iface) {
assertE2I(inter, e, dst)
}
func assertE2E(inter *interfacetype, e eface, r *eface) {
if e._type == nil {
// explicit conversions require non-nil interface value.
panic(&TypeAssertionError{"", "", inter.typ._string, ""})
}
*r = e
}
// The compiler ensures that r is non-nil.
func assertE2E2(inter *interfacetype, e eface, r *eface) bool {
if e._type == nil {
*r = eface{}
return false
}
*r = e
return true
}
func iterate_itabs(fn func(*itab)) {
for _, h := range &hash {
for ; h != nil; h = h.link {
fn(h)
}
}
}