diff --git a/ssa/builder.go b/ssa/builder.go index ad1e370c..4cf400ad 100644 --- a/ssa/builder.go +++ b/ssa/builder.go @@ -686,9 +686,7 @@ func (b *builder) expr(fn *Function, e ast.Expr) Value { // (*T).f or T.f, the method f from the method-set of type T. if fn.Pkg.info.IsType(e.X) { typ := fn.Pkg.typeOf(e.X) - // TODO(adonovan): opt: it's overkill to - // generate the entire method set here. - if m := fn.Prog.MethodSet(typ)[id]; m != nil { + if m := fn.Prog.LookupMethod(typ, id); m != nil { return emitConv(fn, m, fn.Pkg.typeOf(e)) } @@ -776,9 +774,7 @@ func (b *builder) findMethod(fn *Function, base ast.Expr, id string) (*Function, typ := fn.Pkg.typeOf(base) // Consult method-set of X. - // TODO(adonovan): opt: it's overkill to generate the entire - // method set here. - if m := fn.Prog.MethodSet(typ)[id]; m != nil { + if m := fn.Prog.LookupMethod(typ, id); m != nil { aptr := isPointer(typ) fptr := isPointer(m.Signature.Recv().Type()) if aptr == fptr { @@ -791,9 +787,7 @@ func (b *builder) findMethod(fn *Function, base ast.Expr, id string) (*Function, } if !isPointer(typ) { // Consult method-set of *X. - // TODO(adonovan): opt: it's overkill to generate the - // entire method set here. - if m := fn.Prog.MethodSet(types.NewPointer(typ))[id]; m != nil { + if m := fn.Prog.LookupMethod(types.NewPointer(typ), id); m != nil { // A method found only in MS(*X) must have a // pointer formal receiver; but the actual // value is not a pointer. diff --git a/ssa/promote.go b/ssa/promote.go index d48b3469..822e68c8 100644 --- a/ssa/promote.go +++ b/ssa/promote.go @@ -23,14 +23,20 @@ import ( // *T receiver types, etc. // A nil result indicates an empty set. // -// TODO(adonovan): opt: most uses of MethodSet() only access one -// member; only generation of object code for MakeInterface needs them -// all. Build it incrementally. -// // Thread-safe. // func (prog *Program) MethodSet(typ types.Type) MethodSet { - if typ.MethodSet().Len() == 0 { + return prog.populateMethodSet(typ, "") +} + +// populateMethodSet returns the method set for typ, ensuring that it +// contains at least key id. If id is empty, the entire method set is +// populated. +// +func (prog *Program) populateMethodSet(typ types.Type, id string) MethodSet { + tmset := typ.MethodSet() + n := tmset.Len() + if n == 0 { return nil } if _, ok := deref(typ).Underlying().(*types.Interface); ok { @@ -38,31 +44,55 @@ func (prog *Program) MethodSet(typ types.Type) MethodSet { // has no methods---yet go/types says it has! return nil } - if !canHaveConcreteMethods(typ, true) { - return nil - } if prog.mode&LogSource != 0 { - defer logStack("MethodSet %s", typ)() + defer logStack("MethodSet %s id=%s", typ, id)() } prog.methodsMu.Lock() defer prog.methodsMu.Unlock() - if mset := prog.methodSets.At(typ); mset != nil { - return mset.(MethodSet) // cache hit + mset, _ := prog.methodSets.At(typ).(MethodSet) + if mset == nil { + mset = make(MethodSet) + prog.methodSets.Set(typ, mset) } - mset := make(MethodSet) - tmset := typ.MethodSet() - for i, n := 0, tmset.Len(); i < n; i++ { - obj := tmset.At(i) - mset[obj.Func.Id()] = makeMethod(prog, typ, obj) + if len(mset) < n { + if id != "" { // single method + // tmset.Lookup() is no use to us with only an Id string. + if mset[id] == nil { + for i := 0; i < n; i++ { + obj := tmset.At(i) + if obj.Id() == id { + mset[id] = makeMethod(prog, typ, obj) + return mset + } + } + } + } + + // complete set + for i := 0; i < n; i++ { + obj := tmset.At(i) + if id := obj.Id(); mset[id] == nil { + mset[id] = makeMethod(prog, typ, obj) + } + } } - prog.methodSets.Set(typ, mset) + return mset } +// LookupMethod returns the method id of type typ, building wrapper +// methods on demand. It returns nil if the typ has no such method. +// +// Thread-safe. +// +func (prog *Program) LookupMethod(typ types.Type, id string) *Function { + return prog.populateMethodSet(typ, id)[id] +} + // makeMethod returns the concrete Function for the method obj, // adapted if necessary so that its receiver type is typ. // diff --git a/ssa/util.go b/ssa/util.go index bb7d5b0e..d077da9a 100644 --- a/ssa/util.go +++ b/ssa/util.go @@ -106,26 +106,6 @@ outer: return true } -// canHaveConcreteMethods returns true iff typ may have concrete -// methods associated with it. Callers must supply allowPtr=true. -// -// TODO(gri): consider putting this in go/types. It's surprisingly subtle. -func canHaveConcreteMethods(typ types.Type, allowPtr bool) bool { - switch typ := typ.(type) { - case *types.Pointer: - return allowPtr && canHaveConcreteMethods(typ.Elem(), false) - case *types.Named: - switch typ.Underlying().(type) { - case *types.Pointer, *types.Interface: - return false - } - return true - case *types.Struct: - return true - } - return false -} - // DefaultType returns the default "typed" type for an "untyped" type; // it returns the incoming type for all other types. The default type // for untyped nil is untyped nil.