diff --git a/cmd/vet/copylock.go b/cmd/vet/copylock.go index ebe17c872c..d5c20e0874 100644 --- a/cmd/vet/copylock.go +++ b/cmd/vet/copylock.go @@ -82,8 +82,8 @@ func lockPath(tpkg *types.Package, typ types.Type) typePath { // We're looking for cases in which a reference to this type // can be locked, but a value cannot. This differentiates // embedded interfaces from embedded values. - if plock := types.NewPointer(typ).MethodSet().Lookup(tpkg, "Lock"); plock != nil { - if lock := typ.MethodSet().Lookup(tpkg, "Lock"); lock == nil { + if plock := types.NewMethodSet(types.NewPointer(typ)).Lookup(tpkg, "Lock"); plock != nil { + if lock := types.NewMethodSet(typ).Lookup(tpkg, "Lock"); lock == nil { return []types.Type{typ} } } diff --git a/cmd/vet/types.go b/cmd/vet/types.go index 10fdaf47ea..dce66dd50b 100644 --- a/cmd/vet/types.go +++ b/cmd/vet/types.go @@ -310,7 +310,7 @@ func (f *File) isErrorMethodCall(call *ast.CallExpr) bool { // It is part of the workaround for Formatters and should be deleted when // that workaround is no longer necessary. TODO: delete when fixed. func hasMethod(typ types.Type, name string) bool { - set := typ.MethodSet() + set := types.NewMethodSet(typ) for i := 0; i < set.Len(); i++ { if set.At(i).Obj().Name() == name { return true diff --git a/go/gcimporter/gcimporter_test.go b/go/gcimporter/gcimporter_test.go index 37b439a825..877489bed8 100644 --- a/go/gcimporter/gcimporter_test.go +++ b/go/gcimporter/gcimporter_test.go @@ -207,7 +207,7 @@ func TestCorrectMethodPackage(t *testing.T) { } mutex := imports["sync"].Scope().Lookup("Mutex").(*types.TypeName).Type() - mset := types.NewPointer(mutex).MethodSet() // methods of *sync.Mutex + mset := types.NewMethodSet(types.NewPointer(mutex)) // methods of *sync.Mutex sel := mset.Lookup(nil, "Lock") lock := sel.Obj().(*types.Func) if got, want := lock.Pkg().Path(), "sync"; got != want { diff --git a/go/pointer/analysis.go b/go/pointer/analysis.go index 1995c8de20..4cc310b1c9 100644 --- a/go/pointer/analysis.go +++ b/go/pointer/analysis.go @@ -278,7 +278,7 @@ func Analyze(config *Config) *Result { if reflect := a.prog.ImportedPackage("reflect"); reflect != nil { rV := reflect.Object.Scope().Lookup("Value") a.reflectValueObj = rV - a.reflectValueCall = a.prog.Method(rV.Type().MethodSet().Lookup(nil, "Call")) + a.reflectValueCall = a.prog.Method(a.prog.MethodSets.MethodSet(rV.Type()).Lookup(nil, "Call")) a.reflectType = reflect.Object.Scope().Lookup("Type").Type().(*types.Named) a.reflectRtypeObj = reflect.Object.Scope().Lookup("rtype") a.reflectRtypePtr = types.NewPointer(a.reflectRtypeObj.Type()) diff --git a/go/pointer/gen.go b/go/pointer/gen.go index a9e1842901..ce190d1c73 100644 --- a/go/pointer/gen.go +++ b/go/pointer/gen.go @@ -720,7 +720,7 @@ func (a *analysis) genInvokeReflectType(caller *cgnode, site *callsite, call *ss a.typeAssert(a.reflectRtypePtr, rtype, recv, true) // Look up the concrete method. - meth := a.reflectRtypePtr.MethodSet().Lookup(call.Method.Pkg(), call.Method.Name()) + meth := a.prog.MethodSets.MethodSet(a.reflectRtypePtr).Lookup(call.Method.Pkg(), call.Method.Name()) fn := a.prog.Method(meth) obj := a.makeFunctionObject(fn, site) // new contour for this call @@ -1209,7 +1209,7 @@ func (a *analysis) genFunc(cgn *cgnode) { // genMethodsOf generates nodes and constraints for all methods of type T. func (a *analysis) genMethodsOf(T types.Type) { - mset := T.MethodSet() + mset := a.prog.MethodSets.MethodSet(T) for i, n := 0, mset.Len(); i < n; i++ { a.valueNode(a.prog.Method(mset.At(i))) } diff --git a/go/pointer/reflect.go b/go/pointer/reflect.go index 110cf06dbf..ec71d9e892 100644 --- a/go/pointer/reflect.go +++ b/go/pointer/reflect.go @@ -1568,7 +1568,7 @@ func (c *rtypeMethodByNameConstraint) solve(a *analysis, _ *node, delta nodeset) // We don't use Lookup(c.name) when c.name != "" to avoid // ambiguity: >1 unexported methods could match. - mset := T.MethodSet() + mset := a.prog.MethodSets.MethodSet(T) for i, n := 0, mset.Len(); i < n; i++ { sel := mset.At(i) if c.name == "" || c.name == sel.Obj().Name() { diff --git a/go/pointer/solve.go b/go/pointer/solve.go index bfd4f994d8..8ed4aee339 100644 --- a/go/pointer/solve.go +++ b/go/pointer/solve.go @@ -312,7 +312,7 @@ func (c *invokeConstraint) solve(a *analysis, n *node, delta nodeset) { } // Look up the concrete method. - meth := tDyn.MethodSet().Lookup(c.method.Pkg(), c.method.Name()) + meth := a.prog.MethodSets.MethodSet(tDyn).Lookup(c.method.Pkg(), c.method.Name()) if meth == nil { panic(fmt.Sprintf("n%d: type %s has no method %s (iface=n%d)", c.iface, tDyn, c.method, ifaceObj)) diff --git a/go/ssa/builder_test.go b/go/ssa/builder_test.go index 03d77a04bd..82332241a7 100644 --- a/go/ssa/builder_test.go +++ b/go/ssa/builder_test.go @@ -104,7 +104,7 @@ func main() { if !isExt { t.Fatalf("unexpected name type in main package: %s", mem) } - mset := types.NewPointer(mem.Type()).MethodSet() + mset := prog.MethodSets.MethodSet(types.NewPointer(mem.Type())) for i, n := 0, mset.Len(); i < n; i++ { m := prog.Method(mset.At(i)) // For external types, only synthetic wrappers have code. diff --git a/go/ssa/interp/interp.go b/go/ssa/interp/interp.go index 58194ef991..19ece8294c 100644 --- a/go/ssa/interp/interp.go +++ b/go/ssa/interp/interp.go @@ -177,7 +177,7 @@ func lookupMethod(i *interpreter, typ types.Type, meth *types.Func) *ssa.Functio case errorType: return i.errorMethods[meth.Id()] } - return i.prog.Method(typ.MethodSet().Lookup(meth.Pkg(), meth.Name())) + return i.prog.Method(i.prog.MethodSets.MethodSet(typ).Lookup(meth.Pkg(), meth.Name())) } // visitInstr interprets a single ssa.Instruction within the activation diff --git a/go/ssa/interp/reflect.go b/go/ssa/interp/reflect.go index a297eff880..6d42fd9af0 100644 --- a/go/ssa/interp/reflect.go +++ b/go/ssa/interp/reflect.go @@ -119,7 +119,7 @@ func ext۰reflect۰rtype۰NumField(fr *frame, args []value) value { func ext۰reflect۰rtype۰NumMethod(fr *frame, args []value) value { // Signature: func (t reflect.rtype) int - return args[0].(rtype).t.MethodSet().Len() + return fr.i.prog.MethodSets.MethodSet(args[0].(rtype).t).Len() } func ext۰reflect۰rtype۰NumOut(fr *frame, args []value) value { @@ -330,7 +330,7 @@ func ext۰reflect۰Value۰NumField(fr *frame, args []value) value { func ext۰reflect۰Value۰NumMethod(fr *frame, args []value) value { // Signature: func (reflect.Value) int - return rV2T(args[0]).t.MethodSet().Len() + return fr.i.prog.MethodSets.MethodSet(rV2T(args[0]).t).Len() } func ext۰reflect۰Value۰Pointer(fr *frame, args []value) value { diff --git a/go/ssa/print.go b/go/ssa/print.go index 9634fdba43..a902382b71 100644 --- a/go/ssa/print.go +++ b/go/ssa/print.go @@ -407,7 +407,7 @@ func WritePackage(buf *bytes.Buffer, p *Package) { case *Type: fmt.Fprintf(buf, " type %-*s %s\n", maxname, name, types.TypeString(p.Object, mem.Type().Underlying())) - for _, meth := range IntuitiveMethodSet(mem.Type()) { + for _, meth := range IntuitiveMethodSet(mem.Type(), &p.Prog.MethodSets) { fmt.Fprintf(buf, " %s\n", types.SelectionString(p.Object, meth)) } @@ -431,15 +431,15 @@ func WritePackage(buf *bytes.Buffer, p *Package) { // // TODO(gri): move this to go/types? // -func IntuitiveMethodSet(T types.Type) []*types.Selection { +func IntuitiveMethodSet(T types.Type, msets *types.MethodSetCache) []*types.Selection { var result []*types.Selection - mset := T.MethodSet() + mset := msets.MethodSet(T) if _, ok := T.Underlying().(*types.Interface); ok { for i, n := 0, mset.Len(); i < n; i++ { result = append(result, mset.At(i)) } } else { - pmset := types.NewPointer(T).MethodSet() + pmset := msets.MethodSet(types.NewPointer(T)) for i, n := 0, pmset.Len(); i < n; i++ { meth := pmset.At(i) if m := mset.Lookup(meth.Obj().Pkg(), meth.Obj().Name()); m != nil { diff --git a/go/ssa/promote.go b/go/ssa/promote.go index 40ba77ea2e..5b17e666b4 100644 --- a/go/ssa/promote.go +++ b/go/ssa/promote.go @@ -54,7 +54,7 @@ func (prog *Program) Method(meth *types.Selection) *Function { // EXCLUSIVE_LOCKS_ACQUIRED(prog.methodsMu) // func (prog *Program) makeMethods(T types.Type) bool { - tmset := T.MethodSet() + tmset := prog.MethodSets.MethodSet(T) n := tmset.Len() if n == 0 { return false // empty (common case) diff --git a/go/ssa/source.go b/go/ssa/source.go index 6dd667e487..c98dae8c51 100644 --- a/go/ssa/source.go +++ b/go/ssa/source.go @@ -119,7 +119,7 @@ func findNamedFunc(pkg *Package, pos token.Pos) *Function { return mem } case *Type: - mset := types.NewPointer(mem.Type()).MethodSet() + mset := pkg.Prog.MethodSets.MethodSet(types.NewPointer(mem.Type())) for i, n := 0, mset.Len(); i < n; i++ { // Don't call Program.Method: avoid creating wrappers. obj := mset.At(i).Obj().(*types.Func) @@ -202,7 +202,7 @@ func (prog *Program) FuncValue(obj *types.Func) *Function { return v.(*Function) } // Interface method wrapper? - meth := recvType(obj).MethodSet().Lookup(obj.Pkg(), obj.Name()) + meth := prog.MethodSets.MethodSet(recvType(obj)).Lookup(obj.Pkg(), obj.Name()) return prog.Method(meth) } diff --git a/go/ssa/ssa.go b/go/ssa/ssa.go index 1b153c972f..cfa06e5ae4 100644 --- a/go/ssa/ssa.go +++ b/go/ssa/ssa.go @@ -22,10 +22,11 @@ import ( // A Program is a partial or complete Go program converted to SSA form. // type Program struct { - Fset *token.FileSet // position information for the files of this Program - imported map[string]*Package // all importable Packages, keyed by import path - packages map[*types.Package]*Package // all loaded Packages, keyed by object - mode BuilderMode // set of mode bits for SSA construction + Fset *token.FileSet // position information for the files of this Program + imported map[string]*Package // all importable Packages, keyed by import path + packages map[*types.Package]*Package // all loaded Packages, keyed by object + mode BuilderMode // set of mode bits for SSA construction + MethodSets types.MethodSetCache // cache of type-checker's method-sets methodsMu sync.Mutex // guards the following maps: methodSets typemap.M // maps type to its concrete methodSet @@ -612,8 +613,8 @@ type ChangeInterface struct { // MakeInterface constructs an instance of an interface type from a // value of a concrete type. // -// Use X.Type().MethodSet() to find the method-set of X, and -// Program.Method(m) to find the implementation of a method. +// Use Program.MethodSets.MethodSet(X.Type()) to find the method-set +// of X, and Program.Method(m) to find the implementation of a method. // // To construct the zero value of an interface type T, use: // NewConst(exact.MakeNil(), T, pos) diff --git a/go/ssa/ssautil/visit.go b/go/ssa/ssautil/visit.go index f202148bba..6b8c5d21a8 100644 --- a/go/ssa/ssautil/visit.go +++ b/go/ssa/ssautil/visit.go @@ -42,7 +42,7 @@ func (visit *visitor) program() { } } for _, T := range visit.prog.TypesWithMethodSets() { - mset := T.MethodSet() + mset := visit.prog.MethodSets.MethodSet(T) for i, n := 0, mset.Len(); i < n; i++ { visit.function(visit.prog.Method(mset.At(i))) } diff --git a/go/types/call.go b/go/types/call.go index 823693d3e2..2773e90af0 100644 --- a/go/types/call.go +++ b/go/types/call.go @@ -392,7 +392,7 @@ func (check *checker) selector(x *operand, e *ast.SelectorExpr) { // TODO(gri) Consider also using a method set cache for the lifetime // of checker once we rely on MethodSet lookup instead of individual // lookup. - mset := typ.MethodSet() + mset := NewMethodSet(typ) if m := mset.Lookup(check.pkg, sel); m == nil || m.obj != obj { check.dump("%s: (%s).%v -> %s", e.Pos(), typ, obj.name, m) check.dump("%s\n", mset) diff --git a/go/types/methodset.go b/go/types/methodset.go index 4ecd6f5daf..9ded496efa 100644 --- a/go/types/methodset.go +++ b/go/types/methodset.go @@ -10,7 +10,6 @@ import ( "bytes" "fmt" "sort" - "sync" ) // A MethodSet is an ordered set of concrete or abstract (interface) methods; @@ -63,31 +62,11 @@ func (s *MethodSet) Lookup(pkg *Package, name string) *Selection { // Shared empty method set. var emptyMethodSet MethodSet -// A cachedMethodSet provides access to a method set -// for a given type by computing it once on demand, -// and then caching it for future use. Threadsafe. -type cachedMethodSet struct { - mset *MethodSet - mu sync.RWMutex // protects mset -} - -// Of returns the (possibly cached) method set for typ. -// Threadsafe. -func (c *cachedMethodSet) of(typ Type) *MethodSet { - c.mu.RLock() - mset := c.mset - c.mu.RUnlock() - if mset == nil { - mset = NewMethodSet(typ) - c.mu.Lock() - c.mset = mset - c.mu.Unlock() - } - return mset -} - -// NewMethodSet computes the method set for the given type T. -// It always returns a non-nil method set, even if it is empty. +// NewMethodSet returns the method set for the given type T. It +// always returns a non-nil method set, even if it is empty. +// +// A MethodSetCache handles repeat queries more efficiently. +// func NewMethodSet(T Type) *MethodSet { // WARNING: The code in this function is extremely subtle - do not modify casually! // This function and lookupFieldOrMethod should be kept in sync. diff --git a/go/types/methodsetcache.go b/go/types/methodsetcache.go new file mode 100644 index 0000000000..5a482e952a --- /dev/null +++ b/go/types/methodsetcache.go @@ -0,0 +1,69 @@ +// 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. + +// This file implements a cache of method sets. + +package types + +import "sync" + +// A MethodSetCache records the method set of each type T for which +// MethodSet(T) is called so that repeat queries are fast. +// The zero value is a ready-to-use cache instance. +type MethodSetCache struct { + mu sync.Mutex + named map[*Named]struct{ value, pointer *MethodSet } // method sets for named N and *N + others map[Type]*MethodSet // all other types +} + +// MethodSet returns the method set of type T. It is thread-safe. +// +// If cache is nil, this function is equivalent to NewMethodSet(T). +// Utility functions can thus expose an optional *MethodSetCache +// parameter to clients that care about performance. +// +func (cache *MethodSetCache) MethodSet(T Type) *MethodSet { + if cache == nil { + return NewMethodSet(T) + } + cache.mu.Lock() + defer cache.mu.Unlock() + + switch T := T.(type) { + case *Named: + return cache.lookupNamed(T).value + + case *Pointer: + if N, ok := T.Elem().(*Named); ok { + return cache.lookupNamed(N).pointer + } + } + + // all other types + // (The map uses pointer equivalence, not type identity.) + mset := cache.others[T] + if mset == nil { + mset = NewMethodSet(T) + if cache.others == nil { + cache.others = make(map[Type]*MethodSet) + } + cache.others[T] = mset + } + return mset +} + +func (cache *MethodSetCache) lookupNamed(named *Named) struct{ value, pointer *MethodSet } { + if cache.named == nil { + cache.named = make(map[*Named]struct{ value, pointer *MethodSet }) + } + // Avoid recomputing mset(*T) for each distinct Pointer + // instance whose underlying type is a named type. + msets, ok := cache.named[named] + if !ok { + msets.value = NewMethodSet(named) + msets.pointer = NewMethodSet(NewPointer(named)) + cache.named[named] = msets + } + return msets +} diff --git a/go/types/type.go b/go/types/type.go index 0a0b22eec1..5aeb416e36 100644 --- a/go/types/type.go +++ b/go/types/type.go @@ -14,9 +14,6 @@ type Type interface { // Underlying returns the underlying type of a type. Underlying() Type - // MethodSet returns the method set of a type. - MethodSet() *MethodSet - // String returns a string representation of a type. String() string } @@ -126,8 +123,7 @@ type Struct struct { fields []*Var tags []string // field tags; nil if there are no tags // TODO(gri) access to offsets is not threadsafe - fix this - offsets []int64 // field offsets in bytes, lazily initialized - mset cachedMethodSet // method set, lazily initialized + offsets []int64 // field offsets in bytes, lazily initialized } // NewStruct returns a new struct with the given fields and corresponding field tags. @@ -163,8 +159,7 @@ func (s *Struct) Tag(i int) string { // A Pointer represents a pointer type. type Pointer struct { - base Type // element type - mset cachedMethodSet // method set, lazily initialized + base Type // element type } // NewPointer returns a new pointer type for the given element (base) type. @@ -249,8 +244,7 @@ type Interface struct { methods []*Func // ordered list of explicitly declared methods embeddeds []*Named // ordered list of explicitly embedded types - allMethods []*Func // ordered list of methods declared with or embedded in this interface (TODO(gri): replace with mset) - mset cachedMethodSet // method set for interface, lazily initialized + allMethods []*Func // ordered list of methods declared with or embedded in this interface (TODO(gri): replace with mset) } // NewInterface returns a new interface for the given methods and embedded types. @@ -364,10 +358,9 @@ func (c *Chan) Elem() Type { return c.elem } // A Named represents a named type. type Named struct { - obj *TypeName // corresponding declared object - underlying Type // possibly a *Named during setup; never a *Named once set up completely - methods []*Func // methods declared for this type (not the method set of this type) - mset, pmset cachedMethodSet // method set for T, *T, lazily initialized + obj *TypeName // corresponding declared object + underlying Type // possibly a *Named during setup; never a *Named once set up completely + methods []*Func // methods declared for this type (not the method set of this type) } // NewNamed returns a new named type for the given type name, underlying type, and associated methods. @@ -426,25 +419,6 @@ func (t *Map) Underlying() Type { return t } func (t *Chan) Underlying() Type { return t } func (t *Named) Underlying() Type { return t.underlying } -func (t *Basic) MethodSet() *MethodSet { return &emptyMethodSet } -func (t *Array) MethodSet() *MethodSet { return &emptyMethodSet } -func (t *Slice) MethodSet() *MethodSet { return &emptyMethodSet } -func (t *Struct) MethodSet() *MethodSet { return t.mset.of(t) } -func (t *Pointer) MethodSet() *MethodSet { - if named, _ := t.base.(*Named); named != nil { - // Avoid recomputing mset(*T) for each distinct Pointer - // instance whose underlying type is a named type. - return named.pmset.of(t) - } - return t.mset.of(t) -} -func (t *Tuple) MethodSet() *MethodSet { return &emptyMethodSet } -func (t *Signature) MethodSet() *MethodSet { return &emptyMethodSet } -func (t *Interface) MethodSet() *MethodSet { return t.mset.of(t) } -func (t *Map) MethodSet() *MethodSet { return &emptyMethodSet } -func (t *Chan) MethodSet() *MethodSet { return &emptyMethodSet } -func (t *Named) MethodSet() *MethodSet { return t.mset.of(t) } - func (t *Basic) String() string { return TypeString(nil, t) } func (t *Array) String() string { return TypeString(nil, t) } func (t *Slice) String() string { return TypeString(nil, t) } diff --git a/oracle/describe.go b/oracle/describe.go index 3d671092b7..fb99ff61e1 100644 --- a/oracle/describe.go +++ b/oracle/describe.go @@ -712,7 +712,7 @@ func pathToString(path []ast.Node) string { func accessibleMethods(t types.Type, from *types.Package) []*types.Selection { var methods []*types.Selection - for _, meth := range ssa.IntuitiveMethodSet(t) { + for _, meth := range ssa.IntuitiveMethodSet(t, nil) { if isAccessibleFrom(meth.Obj(), from) { methods = append(methods, meth) } diff --git a/oracle/implements.go b/oracle/implements.go index d91422739c..2ff69a7dec 100644 --- a/oracle/implements.go +++ b/oracle/implements.go @@ -47,15 +47,17 @@ func implements(o *Oracle, qpos *QueryPos) (queryResult, error) { } allNamed = append(allNamed, types.Universe.Lookup("error").Type()) + var msets types.MethodSetCache + // Test each named type. var to, from, fromPtr []types.Type for _, U := range allNamed { if isInterface(T) { - if T.MethodSet().Len() == 0 { + if msets.MethodSet(T).Len() == 0 { continue // empty interface } if isInterface(U) { - if U.MethodSet().Len() == 0 { + if msets.MethodSet(U).Len() == 0 { continue // empty interface } @@ -77,7 +79,7 @@ func implements(o *Oracle, qpos *QueryPos) (queryResult, error) { } } } else if isInterface(U) { - if U.MethodSet().Len() == 0 { + if msets.MethodSet(U).Len() == 0 { continue // empty interface } @@ -113,7 +115,7 @@ type implementsResult struct { func (r *implementsResult) display(printf printfFunc) { if isInterface(r.t) { - if r.t.MethodSet().Len() == 0 { + if types.NewMethodSet(r.t).Len() == 0 { // TODO(adonovan): cache mset printf(r.pos, "empty interface type %s", r.t) return }