Skip to content

Commit 86a3389

Browse files
committed
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/100846 Reviewed-by: Ian Lance Taylor <[email protected]>
1 parent 91bbe53 commit 86a3389

File tree

5 files changed

+60
-75
lines changed

5 files changed

+60
-75
lines changed

src/cmd/compile/internal/gc/reflect.go

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -51,21 +51,19 @@ type Sig struct {
5151
offset int32
5252
}
5353

54-
// siglt sorts method signatures by name, then package path.
54+
// siglt sorts method signatures by name with exported methods first,
55+
// and then non-exported methods by their package path.
5556
func siglt(a, b *Sig) bool {
57+
if (a.pkg == nil) != (b.pkg == nil) {
58+
return a.pkg == nil
59+
}
5660
if a.name != b.name {
5761
return a.name < b.name
5862
}
59-
if a.pkg == b.pkg {
60-
return false
61-
}
62-
if a.pkg == nil {
63-
return true
64-
}
65-
if b.pkg == nil {
66-
return false
63+
if a.pkg != nil && a.pkg != b.pkg {
64+
return a.pkg.Path < b.pkg.Path
6765
}
68-
return a.pkg.Path < b.pkg.Path
66+
return false
6967
}
7068

7169
// Builds a type representing a Bucket structure for
@@ -403,7 +401,7 @@ func methods(t *types.Type) []*Sig {
403401

404402
method := f.Sym
405403
if method == nil {
406-
continue
404+
break
407405
}
408406

409407
// get receiver type for this particular method.
@@ -683,12 +681,13 @@ func dextratype(lsym *obj.LSym, ot int, t *types.Type, dataAdd int) int {
683681
if mcount != int(uint16(mcount)) {
684682
Fatalf("too many methods on %v: %d", t, mcount)
685683
}
684+
xcount := sort.Search(mcount, func(i int) bool { return m[i].pkg != nil })
686685
if dataAdd != int(uint32(dataAdd)) {
687686
Fatalf("methods are too far away on %v: %d", t, dataAdd)
688687
}
689688

690689
ot = duint16(lsym, ot, uint16(mcount))
691-
ot = duint16(lsym, ot, 0)
690+
ot = duint16(lsym, ot, uint16(xcount))
692691
ot = duint32(lsym, ot, uint32(dataAdd))
693692
ot = duint32(lsym, ot, 0)
694693
return ot

src/cmd/compile/internal/gc/reflect_test.go

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,23 +14,27 @@ import (
1414
func TestSortingBySigLT(t *testing.T) {
1515
data := []*Sig{
1616
&Sig{name: "b", pkg: &types.Pkg{Path: "abc"}},
17-
&Sig{name: "b", pkg: nil},
18-
&Sig{name: "c", pkg: nil},
17+
&Sig{name: "B", pkg: nil},
18+
&Sig{name: "C", pkg: nil},
1919
&Sig{name: "c", pkg: &types.Pkg{Path: "uvw"}},
20-
&Sig{name: "c", pkg: nil},
20+
&Sig{name: "C", pkg: nil},
21+
&Sig{name: "φ", pkg: &types.Pkg{Path: "gr"}},
22+
&Sig{name: "Φ", pkg: nil},
2123
&Sig{name: "b", pkg: &types.Pkg{Path: "xyz"}},
2224
&Sig{name: "a", pkg: &types.Pkg{Path: "abc"}},
23-
&Sig{name: "b", pkg: nil},
25+
&Sig{name: "B", pkg: nil},
2426
}
2527
want := []*Sig{
28+
&Sig{name: "B", pkg: nil},
29+
&Sig{name: "B", pkg: nil},
30+
&Sig{name: "C", pkg: nil},
31+
&Sig{name: "C", pkg: nil},
32+
&Sig{name: "Φ", pkg: nil},
2633
&Sig{name: "a", pkg: &types.Pkg{Path: "abc"}},
27-
&Sig{name: "b", pkg: nil},
28-
&Sig{name: "b", pkg: nil},
2934
&Sig{name: "b", pkg: &types.Pkg{Path: "abc"}},
3035
&Sig{name: "b", pkg: &types.Pkg{Path: "xyz"}},
31-
&Sig{name: "c", pkg: nil},
32-
&Sig{name: "c", pkg: nil},
3336
&Sig{name: "c", pkg: &types.Pkg{Path: "uvw"}},
37+
&Sig{name: "φ", pkg: &types.Pkg{Path: "gr"}},
3438
}
3539
if len(data) != len(want) {
3640
t.Fatal("want and data must match")

src/cmd/compile/internal/gc/subr.go

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -373,30 +373,40 @@ func saveorignode(n *Node) {
373373
n.Orig = norig
374374
}
375375

376-
// methcmp sorts by symbol, then by package path for unexported symbols.
376+
// methcmp sorts methods by name with exported methods first,
377+
// and then non-exported methods by their package path.
377378
type methcmp []*types.Field
378379

379380
func (x methcmp) Len() int { return len(x) }
380381
func (x methcmp) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
381382
func (x methcmp) Less(i, j int) bool {
382383
a := x[i]
383384
b := x[j]
384-
if a.Sym == nil && b.Sym == nil {
385+
if a.Sym == b.Sym {
385386
return false
386387
}
388+
389+
// Blank methods to the end.
387390
if a.Sym == nil {
388-
return true
391+
return false
389392
}
390393
if b.Sym == nil {
391-
return false
394+
return true
392395
}
396+
397+
// Exported methods to the front.
398+
ea := exportname(a.Sym.Name)
399+
eb := exportname(b.Sym.Name)
400+
if ea != eb {
401+
return ea
402+
}
403+
404+
// Sort by name and then package.
393405
if a.Sym.Name != b.Sym.Name {
394406
return a.Sym.Name < b.Sym.Name
395407
}
396-
if !exportname(a.Sym.Name) {
397-
if a.Sym.Pkg.Path != b.Sym.Pkg.Path {
398-
return a.Sym.Pkg.Path < b.Sym.Pkg.Path
399-
}
408+
if !ea && a.Sym.Pkg.Path != b.Sym.Pkg.Path {
409+
return a.Sym.Pkg.Path < b.Sym.Pkg.Path
400410
}
401411

402412
return false

src/reflect/type.go

Lines changed: 18 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,7 @@ type method struct {
333333
type uncommonType struct {
334334
pkgPath nameOff // import path; empty for built-in types like int, string
335335
mcount uint16 // number of methods
336-
_ uint16 // unused
336+
xcount uint16 // number of exported methods
337337
moff uint32 // offset from this uncommontype to [mcount]method
338338
_ uint32 // unused
339339
}
@@ -639,6 +639,13 @@ func (t *uncommonType) methods() []method {
639639
return (*[1 << 16]method)(add(unsafe.Pointer(t), uintptr(t.moff), "t.mcount > 0"))[:t.mcount:t.mcount]
640640
}
641641

642+
func (t *uncommonType) exportedMethods() []method {
643+
if t.xcount == 0 {
644+
return nil
645+
}
646+
return (*[1 << 16]method)(add(unsafe.Pointer(t), uintptr(t.moff), "t.xcount > 0"))[:t.xcount:t.xcount]
647+
}
648+
642649
// resolveNameOff resolves a name offset from a base pointer.
643650
// The (*rtype).nameOff method is a convenience wrapper for this function.
644651
// Implemented in the runtime package.
@@ -783,53 +790,19 @@ func (t *rtype) pointers() bool { return t.kind&kindNoPointers == 0 }
783790

784791
func (t *rtype) common() *rtype { return t }
785792

786-
var methodCache sync.Map // map[*rtype][]method
787-
788793
func (t *rtype) exportedMethods() []method {
789-
methodsi, found := methodCache.Load(t)
790-
if found {
791-
return methodsi.([]method)
792-
}
793-
794794
ut := t.uncommon()
795795
if ut == nil {
796796
return nil
797797
}
798-
allm := ut.methods()
799-
allExported := true
800-
for _, m := range allm {
801-
name := t.nameOff(m.name)
802-
if !name.isExported() {
803-
allExported = false
804-
break
805-
}
806-
}
807-
var methods []method
808-
if allExported {
809-
methods = allm
810-
} else {
811-
methods = make([]method, 0, len(allm))
812-
for _, m := range allm {
813-
name := t.nameOff(m.name)
814-
if name.isExported() {
815-
methods = append(methods, m)
816-
}
817-
}
818-
methods = methods[:len(methods):len(methods)]
819-
}
820-
821-
methodsi, _ = methodCache.LoadOrStore(t, methods)
822-
return methodsi.([]method)
798+
return ut.exportedMethods()
823799
}
824800

825801
func (t *rtype) NumMethod() int {
826802
if t.Kind() == Interface {
827803
tt := (*interfaceType)(unsafe.Pointer(t))
828804
return tt.NumMethod()
829805
}
830-
if t.tflag&tflagUncommon == 0 {
831-
return 0 // avoid methodCache synchronization
832-
}
833806
return len(t.exportedMethods())
834807
}
835808

@@ -876,16 +849,10 @@ func (t *rtype) MethodByName(name string) (m Method, ok bool) {
876849
if ut == nil {
877850
return Method{}, false
878851
}
879-
utmethods := ut.methods()
880-
var eidx int
881-
for i := 0; i < int(ut.mcount); i++ {
882-
p := utmethods[i]
883-
pname := t.nameOff(p.name)
884-
if pname.isExported() {
885-
if pname.name() == name {
886-
return t.Method(eidx), true
887-
}
888-
eidx++
852+
// TODO(mdempsky): Binary search.
853+
for i, p := range ut.exportedMethods() {
854+
if t.nameOff(p.name).name() == name {
855+
return t.Method(i), true
889856
}
890857
}
891858
return Method{}, false
@@ -2627,7 +2594,12 @@ func StructOf(fields []StructField) Type {
26272594
default:
26282595
panic("reflect.StructOf: too many methods")
26292596
}
2597+
// TODO(sbinet): Once we allow embedding multiple types,
2598+
// methods will need to be sorted like the compiler does.
2599+
// TODO(sbinet): Once we allow non-exported methods, we will
2600+
// need to compute xcount as the number of exported methods.
26302601
ut.mcount = uint16(len(methods))
2602+
ut.xcount = ut.mcount
26312603
ut.moff = uint32(unsafe.Sizeof(uncommonType{}))
26322604

26332605
if len(fs) > 0 {

src/runtime/type.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -329,7 +329,7 @@ type method struct {
329329
type uncommontype struct {
330330
pkgpath nameOff
331331
mcount uint16 // number of methods
332-
_ uint16 // unused
332+
xcount uint16 // number of exported methods
333333
moff uint32 // offset from this uncommontype to [mcount]method
334334
_ uint32 // unused
335335
}

0 commit comments

Comments
 (0)