Commit 86a33896 authored by Matthew Dempsky's avatar Matthew Dempsky

reflect: sort exported methods first

By moving exported methods to the front of method lists, filtering
down to only the exported methods just needs a count of how many
exported methods exist, which the compiler can statically
provide. This allows getting rid of the exported method cache.

For #22075.

Change-Id: I8eeb274563a2940e1347c34d673f843ae2569064
Reviewed-on: https://go-review.googlesource.com/100846Reviewed-by: default avatarIan Lance Taylor <iant@golang.org>
parent 91bbe538
...@@ -51,21 +51,19 @@ type Sig struct { ...@@ -51,21 +51,19 @@ type Sig struct {
offset int32 offset int32
} }
// siglt sorts method signatures by name, then package path. // siglt sorts method signatures by name with exported methods first,
// and then non-exported methods by their package path.
func siglt(a, b *Sig) bool { func siglt(a, b *Sig) bool {
if (a.pkg == nil) != (b.pkg == nil) {
return a.pkg == nil
}
if a.name != b.name { if a.name != b.name {
return a.name < b.name return a.name < b.name
} }
if a.pkg == b.pkg { if a.pkg != nil && a.pkg != b.pkg {
return false return a.pkg.Path < b.pkg.Path
}
if a.pkg == nil {
return true
}
if b.pkg == nil {
return false
} }
return a.pkg.Path < b.pkg.Path return false
} }
// Builds a type representing a Bucket structure for // Builds a type representing a Bucket structure for
...@@ -403,7 +401,7 @@ func methods(t *types.Type) []*Sig { ...@@ -403,7 +401,7 @@ func methods(t *types.Type) []*Sig {
method := f.Sym method := f.Sym
if method == nil { if method == nil {
continue break
} }
// get receiver type for this particular method. // get receiver type for this particular method.
...@@ -683,12 +681,13 @@ func dextratype(lsym *obj.LSym, ot int, t *types.Type, dataAdd int) int { ...@@ -683,12 +681,13 @@ func dextratype(lsym *obj.LSym, ot int, t *types.Type, dataAdd int) int {
if mcount != int(uint16(mcount)) { if mcount != int(uint16(mcount)) {
Fatalf("too many methods on %v: %d", t, mcount) Fatalf("too many methods on %v: %d", t, mcount)
} }
xcount := sort.Search(mcount, func(i int) bool { return m[i].pkg != nil })
if dataAdd != int(uint32(dataAdd)) { if dataAdd != int(uint32(dataAdd)) {
Fatalf("methods are too far away on %v: %d", t, dataAdd) Fatalf("methods are too far away on %v: %d", t, dataAdd)
} }
ot = duint16(lsym, ot, uint16(mcount)) ot = duint16(lsym, ot, uint16(mcount))
ot = duint16(lsym, ot, 0) ot = duint16(lsym, ot, uint16(xcount))
ot = duint32(lsym, ot, uint32(dataAdd)) ot = duint32(lsym, ot, uint32(dataAdd))
ot = duint32(lsym, ot, 0) ot = duint32(lsym, ot, 0)
return ot return ot
......
...@@ -14,23 +14,27 @@ import ( ...@@ -14,23 +14,27 @@ import (
func TestSortingBySigLT(t *testing.T) { func TestSortingBySigLT(t *testing.T) {
data := []*Sig{ data := []*Sig{
&Sig{name: "b", pkg: &types.Pkg{Path: "abc"}}, &Sig{name: "b", pkg: &types.Pkg{Path: "abc"}},
&Sig{name: "b", pkg: nil}, &Sig{name: "B", pkg: nil},
&Sig{name: "c", pkg: nil}, &Sig{name: "C", pkg: nil},
&Sig{name: "c", pkg: &types.Pkg{Path: "uvw"}}, &Sig{name: "c", pkg: &types.Pkg{Path: "uvw"}},
&Sig{name: "c", pkg: nil}, &Sig{name: "C", pkg: nil},
&Sig{name: "φ", pkg: &types.Pkg{Path: "gr"}},
&Sig{name: "Φ", pkg: nil},
&Sig{name: "b", pkg: &types.Pkg{Path: "xyz"}}, &Sig{name: "b", pkg: &types.Pkg{Path: "xyz"}},
&Sig{name: "a", pkg: &types.Pkg{Path: "abc"}}, &Sig{name: "a", pkg: &types.Pkg{Path: "abc"}},
&Sig{name: "b", pkg: nil}, &Sig{name: "B", pkg: nil},
} }
want := []*Sig{ want := []*Sig{
&Sig{name: "B", pkg: nil},
&Sig{name: "B", pkg: nil},
&Sig{name: "C", pkg: nil},
&Sig{name: "C", pkg: nil},
&Sig{name: "Φ", pkg: nil},
&Sig{name: "a", pkg: &types.Pkg{Path: "abc"}}, &Sig{name: "a", pkg: &types.Pkg{Path: "abc"}},
&Sig{name: "b", pkg: nil},
&Sig{name: "b", pkg: nil},
&Sig{name: "b", pkg: &types.Pkg{Path: "abc"}}, &Sig{name: "b", pkg: &types.Pkg{Path: "abc"}},
&Sig{name: "b", pkg: &types.Pkg{Path: "xyz"}}, &Sig{name: "b", pkg: &types.Pkg{Path: "xyz"}},
&Sig{name: "c", pkg: nil},
&Sig{name: "c", pkg: nil},
&Sig{name: "c", pkg: &types.Pkg{Path: "uvw"}}, &Sig{name: "c", pkg: &types.Pkg{Path: "uvw"}},
&Sig{name: "φ", pkg: &types.Pkg{Path: "gr"}},
} }
if len(data) != len(want) { if len(data) != len(want) {
t.Fatal("want and data must match") t.Fatal("want and data must match")
......
...@@ -373,7 +373,8 @@ func saveorignode(n *Node) { ...@@ -373,7 +373,8 @@ func saveorignode(n *Node) {
n.Orig = norig n.Orig = norig
} }
// methcmp sorts by symbol, then by package path for unexported symbols. // methcmp sorts methods by name with exported methods first,
// and then non-exported methods by their package path.
type methcmp []*types.Field type methcmp []*types.Field
func (x methcmp) Len() int { return len(x) } func (x methcmp) Len() int { return len(x) }
...@@ -381,22 +382,31 @@ func (x methcmp) Swap(i, j int) { x[i], x[j] = x[j], x[i] } ...@@ -381,22 +382,31 @@ func (x methcmp) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
func (x methcmp) Less(i, j int) bool { func (x methcmp) Less(i, j int) bool {
a := x[i] a := x[i]
b := x[j] b := x[j]
if a.Sym == nil && b.Sym == nil { if a.Sym == b.Sym {
return false return false
} }
// Blank methods to the end.
if a.Sym == nil { if a.Sym == nil {
return true return false
} }
if b.Sym == nil { if b.Sym == nil {
return false return true
} }
// Exported methods to the front.
ea := exportname(a.Sym.Name)
eb := exportname(b.Sym.Name)
if ea != eb {
return ea
}
// Sort by name and then package.
if a.Sym.Name != b.Sym.Name { if a.Sym.Name != b.Sym.Name {
return a.Sym.Name < b.Sym.Name return a.Sym.Name < b.Sym.Name
} }
if !exportname(a.Sym.Name) { if !ea && a.Sym.Pkg.Path != b.Sym.Pkg.Path {
if a.Sym.Pkg.Path != b.Sym.Pkg.Path { return a.Sym.Pkg.Path < b.Sym.Pkg.Path
return a.Sym.Pkg.Path < b.Sym.Pkg.Path
}
} }
return false return false
......
...@@ -333,7 +333,7 @@ type method struct { ...@@ -333,7 +333,7 @@ type method struct {
type uncommonType struct { type uncommonType struct {
pkgPath nameOff // import path; empty for built-in types like int, string pkgPath nameOff // import path; empty for built-in types like int, string
mcount uint16 // number of methods mcount uint16 // number of methods
_ uint16 // unused xcount uint16 // number of exported methods
moff uint32 // offset from this uncommontype to [mcount]method moff uint32 // offset from this uncommontype to [mcount]method
_ uint32 // unused _ uint32 // unused
} }
...@@ -639,6 +639,13 @@ func (t *uncommonType) methods() []method { ...@@ -639,6 +639,13 @@ func (t *uncommonType) methods() []method {
return (*[1 << 16]method)(add(unsafe.Pointer(t), uintptr(t.moff), "t.mcount > 0"))[:t.mcount:t.mcount] return (*[1 << 16]method)(add(unsafe.Pointer(t), uintptr(t.moff), "t.mcount > 0"))[:t.mcount:t.mcount]
} }
func (t *uncommonType) exportedMethods() []method {
if t.xcount == 0 {
return nil
}
return (*[1 << 16]method)(add(unsafe.Pointer(t), uintptr(t.moff), "t.xcount > 0"))[:t.xcount:t.xcount]
}
// resolveNameOff resolves a name offset from a base pointer. // resolveNameOff resolves a name offset from a base pointer.
// The (*rtype).nameOff method is a convenience wrapper for this function. // The (*rtype).nameOff method is a convenience wrapper for this function.
// Implemented in the runtime package. // Implemented in the runtime package.
...@@ -783,43 +790,12 @@ func (t *rtype) pointers() bool { return t.kind&kindNoPointers == 0 } ...@@ -783,43 +790,12 @@ func (t *rtype) pointers() bool { return t.kind&kindNoPointers == 0 }
func (t *rtype) common() *rtype { return t } func (t *rtype) common() *rtype { return t }
var methodCache sync.Map // map[*rtype][]method
func (t *rtype) exportedMethods() []method { func (t *rtype) exportedMethods() []method {
methodsi, found := methodCache.Load(t)
if found {
return methodsi.([]method)
}
ut := t.uncommon() ut := t.uncommon()
if ut == nil { if ut == nil {
return nil return nil
} }
allm := ut.methods() return ut.exportedMethods()
allExported := true
for _, m := range allm {
name := t.nameOff(m.name)
if !name.isExported() {
allExported = false
break
}
}
var methods []method
if allExported {
methods = allm
} else {
methods = make([]method, 0, len(allm))
for _, m := range allm {
name := t.nameOff(m.name)
if name.isExported() {
methods = append(methods, m)
}
}
methods = methods[:len(methods):len(methods)]
}
methodsi, _ = methodCache.LoadOrStore(t, methods)
return methodsi.([]method)
} }
func (t *rtype) NumMethod() int { func (t *rtype) NumMethod() int {
...@@ -827,9 +803,6 @@ func (t *rtype) NumMethod() int { ...@@ -827,9 +803,6 @@ func (t *rtype) NumMethod() int {
tt := (*interfaceType)(unsafe.Pointer(t)) tt := (*interfaceType)(unsafe.Pointer(t))
return tt.NumMethod() return tt.NumMethod()
} }
if t.tflag&tflagUncommon == 0 {
return 0 // avoid methodCache synchronization
}
return len(t.exportedMethods()) return len(t.exportedMethods())
} }
...@@ -876,16 +849,10 @@ func (t *rtype) MethodByName(name string) (m Method, ok bool) { ...@@ -876,16 +849,10 @@ func (t *rtype) MethodByName(name string) (m Method, ok bool) {
if ut == nil { if ut == nil {
return Method{}, false return Method{}, false
} }
utmethods := ut.methods() // TODO(mdempsky): Binary search.
var eidx int for i, p := range ut.exportedMethods() {
for i := 0; i < int(ut.mcount); i++ { if t.nameOff(p.name).name() == name {
p := utmethods[i] return t.Method(i), true
pname := t.nameOff(p.name)
if pname.isExported() {
if pname.name() == name {
return t.Method(eidx), true
}
eidx++
} }
} }
return Method{}, false return Method{}, false
...@@ -2627,7 +2594,12 @@ func StructOf(fields []StructField) Type { ...@@ -2627,7 +2594,12 @@ func StructOf(fields []StructField) Type {
default: default:
panic("reflect.StructOf: too many methods") panic("reflect.StructOf: too many methods")
} }
// TODO(sbinet): Once we allow embedding multiple types,
// methods will need to be sorted like the compiler does.
// TODO(sbinet): Once we allow non-exported methods, we will
// need to compute xcount as the number of exported methods.
ut.mcount = uint16(len(methods)) ut.mcount = uint16(len(methods))
ut.xcount = ut.mcount
ut.moff = uint32(unsafe.Sizeof(uncommonType{})) ut.moff = uint32(unsafe.Sizeof(uncommonType{}))
if len(fs) > 0 { if len(fs) > 0 {
......
...@@ -329,7 +329,7 @@ type method struct { ...@@ -329,7 +329,7 @@ type method struct {
type uncommontype struct { type uncommontype struct {
pkgpath nameOff pkgpath nameOff
mcount uint16 // number of methods mcount uint16 // number of methods
_ uint16 // unused xcount uint16 // number of exported methods
moff uint32 // offset from this uncommontype to [mcount]method moff uint32 // offset from this uncommontype to [mcount]method
_ uint32 // unused _ uint32 // unused
} }
......
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